C++ code and Python bindings for TPR parameter read/write.
authorM. Eric Irrgang <ericirrgang@gmail.com>
Tue, 11 Jun 2019 12:40:28 +0000 (15:40 +0300)
committerMark Abraham <mark.j.abraham@gmail.com>
Thu, 22 Aug 2019 13:04:29 +0000 (15:04 +0200)
Core functionality to allow TPR files to be rewritten with altered
parameters, ported from https://github.com/kassonlab/gmxapi v0.0.7.4
with modifications to naming and coding style. (Refer to patch history.)

A lot of the C++ code is not intended to be a long term solution,
but demonstrates the use cases that will need to be addressed
as modules become able to self-describe their inputs and outputs.

However, this change is necessary to support near-term data portability
between gmxapi operations for preparing and wrangling chains or
ensembles of simulations.

Refs #2993

Change-Id: I54677c861dfb19c9f34b11d2c30456e6ee5dbe8d

14 files changed:
python_packaging/src/CMakeLists.txt
python_packaging/src/gmxapi/compat_exceptions.h [new file with mode: 0644]
python_packaging/src/gmxapi/export_tprfile.cpp [new file with mode: 0644]
python_packaging/src/gmxapi/mdparams.h [new file with mode: 0644]
python_packaging/src/gmxapi/module.cpp
python_packaging/src/gmxapi/module.h
python_packaging/src/gmxapi/simulation/__init__.py [new file with mode: 0644]
python_packaging/src/gmxapi/simulation/fileio.py [new file with mode: 0644]
python_packaging/src/gmxapi/tprfile.cpp [new file with mode: 0644]
python_packaging/src/gmxapi/tprfile.h [new file with mode: 0644]
python_packaging/src/gmxapi/typetemplates.h [new file with mode: 0644]
python_packaging/src/test/test_fileio.py [new file with mode: 0644]
src/api/cpp/CMakeLists.txt
src/api/cpp/compat_exceptions.cpp [moved from src/api/cpp/exceptions.cpp with 100% similarity]

index 26949f260716dc1956d7d31cf257269b232651f7..a3400b5763b0e5caa197b6eda6700a93975a848d 100644 (file)
@@ -102,8 +102,10 @@ set(GMXAPI_PYTHON_EXTENSION_SOURCES
     gmxapi/export_context.cpp
     gmxapi/export_exceptions.cpp
     gmxapi/export_system.cpp
+    gmxapi/export_tprfile.cpp
     gmxapi/pycontext.cpp
     gmxapi/pysystem.cpp
+    gmxapi/tprfile.cpp
     )
 
 pybind11_add_module(_gmxapi
diff --git a/python_packaging/src/gmxapi/compat_exceptions.h b/python_packaging/src/gmxapi/compat_exceptions.h
new file mode 100644 (file)
index 0000000..cd5f536
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief C++ exceptions for the gmxapi compatibility tools.
+ *
+ * Used internally for the gmxapi compatibility helpers that manage type
+ * mappings of older GROMACS structures. The long-term disposition of this
+ * code is uncertain, but the headers are not likely to be public. If they
+ * do persist in some form, we can integrate the exception heirarchy into
+ * whatever module takes ownership of this code.
+ *
+ * Exceptions defined here should only be caught by code that understands the
+ * implementation details of these compatibility tools. Exposure of these
+ * exceptions outside of the installed object files should be treated as a bug.
+ *
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ * \ingroup module_python
+ */
+
+#ifndef GMXAPICOMPAT_EXCEPTIONS_H
+#define GMXAPICOMPAT_EXCEPTIONS_H
+
+#include <exception>
+#include <string>
+
+namespace gmxapicompat
+{
+
+/*!
+ * \brief Generic exception class for gmxapicompat.
+ */
+class Exception : public std::exception
+{
+    public:
+        using std::exception::exception;
+
+        explicit Exception(const std::string &message) :
+            message_ {message}
+        {}
+        explicit Exception(const char* message) : Exception(std::string(message)) {}
+
+        const char *what() const noexcept override
+        {
+            return message_.c_str();
+        }
+
+    private:
+        std::string message_;
+};
+
+/*!
+ * \brief The key name provided for a key-value mapping is invalid.
+ */
+class KeyError : public Exception
+{
+    using Exception::Exception;
+};
+
+/*!
+ * \brief The value provided for a key-value mapping is invalid.
+ */
+class ValueError : public Exception
+{
+    using Exception::Exception;
+};
+
+/*!
+ * \brief Value provided for a key-value mapping is of an incompatible type.
+ */
+class TypeError : public Exception
+{
+    using Exception::Exception;
+};
+
+}      // end namespace gmxapicompat
+#endif //GMXAPICOMPAT_EXCEPTIONS_H
diff --git a/python_packaging/src/gmxapi/export_tprfile.cpp b/python_packaging/src/gmxapi/export_tprfile.cpp
new file mode 100644 (file)
index 0000000..7ea9a04
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief Exports TPR I/O tools during Python module initialization.
+ *
+ * Provides _gmxapi.SimulationParameters and _gmxapi.TprFile classes, as well
+ * as module functions read_tprfile, write_tprfile, copy_tprfile, and rewrite_tprfile.
+ *
+ * TprFile is a Python object that holds a gmxapicompat::TprReadHandle.
+ *
+ * SimulationParameters is the Python type for data sources providing the
+ * simulation parameters aspect of input to simulation operations.
+ *
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ * \ingroup module_python
+ */
+
+#include "gmxapi/exceptions.h"
+
+#include "module.h"
+#include "mdparams.h"
+#include "tprfile.h"
+
+namespace gmxpy
+{
+
+
+void detail::export_tprfile(pybind11::module &module)
+{
+    namespace py = pybind11;
+    using gmxapicompat::GmxMdParams;
+    using gmxapicompat::TprReadHandle;
+    using gmxapicompat::readTprFile;
+
+    py::class_<GmxMdParams> mdparams(module, "SimulationParameters");
+    // We don't want Python users to create invalid params objects, so don't
+    // export a constructor until we can default initialize a valid one.
+    //    mdparams.def(py::init());
+    mdparams.def("extract",
+                 [](const GmxMdParams &self)
+                 {
+                     py::dict dictionary;
+                     for (const auto &key: gmxapicompat::keys(self))
+                     {
+                         try
+                         {
+                             // TODO: More complete typing and dispatching.
+                             // This only handles the two types described in the initial implementation.
+                             // Less trivial types (strings, maps, arrays) warrant additional
+                             // design discussion before being exposed through an interface
+                             // like this one.
+                             // Also reference https://redmine.gromacs.org/issues/2993
+
+                             // We can use templates and/or tag dispatch in a more complete
+                             // future implementation.
+                             const auto &paramType = gmxapicompat::mdParamToType(key);
+                             if (gmxapicompat::isFloat(paramType))
+                             {
+                                 dictionary[key.c_str()] = extractParam(self, key, double());
+                             }
+                             else if (gmxapicompat::isInt(paramType))
+                             {
+                                 dictionary[key.c_str()] = extractParam(self, key, int64_t());
+                             }
+                         }
+                         catch (const gmxapicompat::ValueError &e)
+                         {
+                             throw gmxapi::ProtocolError(std::string("Unknown parameter: ") + key);
+                         }
+                     }
+                     return dictionary;
+                 },
+                 "Get a dictionary of the parameters.");
+
+    // Overload a setter for each known type and None
+    mdparams.def("set",
+                 [](GmxMdParams* self, const std::string &key, py::int_ value)
+                 {
+                     gmxapicompat::setParam(self, key, py::cast<int64_t>(value));
+                 },
+                 py::arg("key").none(false),
+                 py::arg("value").none(false),
+                 "Use a dictionary to update simulation parameters.");
+    mdparams.def("set",
+                 [](GmxMdParams* self, const std::string &key, py::float_ value)
+                 {
+                     gmxapicompat::setParam(self, key, py::cast<double>(value));
+                 },
+                 py::arg("key").none(false),
+                 py::arg("value").none(false),
+                 "Use a dictionary to update simulation parameters.");
+    mdparams.def("set",
+                 [](GmxMdParams* self, const std::string &key, py::none)
+                 {
+                     // unsetParam(self, key);
+                 },
+                 py::arg("key").none(false),
+                 py::arg("value"),
+                 "Use a dictionary to update simulation parameters.");
+
+
+    py::class_<TprReadHandle> tprfile(module, "TprFile");
+    tprfile.def("params",
+                [](const TprReadHandle &self)
+                {
+                    auto params = gmxapicompat::getMdParams(self);
+                    return params;
+                });
+
+    module.def("read_tprfile",
+               &readTprFile,
+               py::arg("filename"),
+               "Get a handle to a TPR file resource for a given file name.");
+
+    module.def("write_tprfile",
+               [](std::string filename, const GmxMdParams &parameterObject)
+               {
+                   auto tprReadHandle = gmxapicompat::getSourceFileHandle(parameterObject);
+                   auto params = gmxapicompat::getMdParams(tprReadHandle);
+                   auto structure = gmxapicompat::getStructureSource(tprReadHandle);
+                   auto state = gmxapicompat::getSimulationState(tprReadHandle);
+                   auto topology = gmxapicompat::getTopologySource(tprReadHandle);
+                   gmxapicompat::writeTprFile(filename, params, structure, state, topology);
+               },
+               py::arg("filename").none(false),
+               py::arg("parameters"),
+               "Write a new TPR file with the provided data.");
+
+    module.def("copy_tprfile",
+               [](const gmxapicompat::TprReadHandle &input, std::string outFile)
+               {
+                   return gmxpy::copy_tprfile(input, outFile);
+               },
+               py::arg("source"),
+               py::arg("destination"),
+               "Copy a TPR file from `source` to `destination`."
+               );
+
+    module.def("rewrite_tprfile",
+               [](std::string input, std::string output, double end_time)
+               {
+                   return gmxpy::rewrite_tprfile(input, output, end_time);
+               },
+               py::arg("source"),
+               py::arg("destination"),
+               py::arg("end_time"),
+               "Copy a TPR file from `source` to `destination`, replacing `nsteps` with `end_time`.");
+}
+
+} // end namespace gmxpy
diff --git a/python_packaging/src/gmxapi/mdparams.h b/python_packaging/src/gmxapi/mdparams.h
new file mode 100644 (file)
index 0000000..99e7be0
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+#ifndef GMXPY_MDPARAMS_H
+#define GMXPY_MDPARAMS_H
+
+/*! \file
+ * \brief Compatibility header for functionality differences in gmxapi releases.
+ *
+ * Also handle the transitioning installed headers from GROMACS 2019 moving forward.
+ *
+ * \todo Configure for gmxapi 0.0.7, 0.0.8, GROMACS 2019, GROMACS master...
+ *
+ * \defgroup gmxapi_compat
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ * \ingroup gmxapi_compat
+ */
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "compat_exceptions.h"
+
+struct t_inputrec;
+
+/*!
+ * \brief Compatibility code for features that may not be in gmxapi yet.
+ */
+namespace gmxapicompat
+{
+
+
+/*!
+ * \brief Label the types recognized by gmxapi.
+ *
+ * Provide an enumeration to aid in translating data between languages, APIs,
+ * and storage formats.
+ *
+ * \todo The spec should explicitly map these to types in APIs already used.
+ * e.g. MPI, Python, numpy, GROMACS, JSON, etc.
+ * \todo Actually check the size of the types.
+ *
+ * \see https://redmine.gromacs.org/issues/2993 for discussion.
+ */
+enum class GmxapiType
+{
+    NULLTYPE, //! Reserved
+    MAP,      //! Mapping of key name (string) to a value of some MdParamType
+    BOOL,     //! Boolean logical type
+    INT64,    //! 64-bit integer type
+    FLOAT64,  //! 64-bit float type
+    STRING,   //! string with metadata
+    NDARRAY,  //! multi-dimensional array with metadata
+};
+
+
+/*!
+ * \brief Static map of GROMACS MDP user input to normalized "type".
+ *
+ * Note that only fields present in the TPR file are named. Additional names
+ * may be accepted as mdp file entries, but we cannot discern which parameter
+ * name was used from inspection of the TPR file and this is an interim solution
+ * that does not need to support a complete MDP file converter.
+ */
+const std::map<std::string, GmxapiType> simulationParameterTypeMap();
+
+const std::map<std::string, bool t_inputrec::*> boolParams();
+const std::map<std::string, int t_inputrec::*> int32Params();
+const std::map<std::string, float t_inputrec::*> float32Params();
+const std::map<std::string, double t_inputrec::*> float64Params();
+const std::map<std::string, int64_t t_inputrec::*> int64Params();
+
+/*!
+ * \brief Static mapping of parameter names to gmxapi types for GROMACS.
+ *
+ * \param name MDP entry name.
+ * \return enumeration value for known parameters.
+ *
+ * \throws gmxapi_compat::ValueError for parameters with no mapping.
+ */
+GmxapiType mdParamToType(const std::string &name);
+
+// Forward declaration for private implementation class for GmxMdParams
+class GmxMdParamsImpl;
+
+/*!
+ * \brief Handle / manager for GROMACS molecular computation input parameters.
+ *
+ * Interface should be consistent with MDP file entries, but data maps to TPR
+ * file interface. For type safety and simplicity, we don't have generic operator
+ * accessors. Instead, we have templated accessors that throw exceptions when
+ * there is trouble.
+ *
+ * When MDP input is entirely stored in a key-value tree, this class can be a
+ * simple adapter or wrapper. Until then, we need a manually maintained mapping
+ * of MDP entries to TPR data.
+ *
+ * Alternatively, we could update the infrastructure used by list_tpx to provide
+ * more generic output, but our efforts may be better spent in updating the
+ * infrastructure for the key-value tree input system.
+ */
+class GmxMdParams
+{
+    public:
+        GmxMdParams();
+        ~GmxMdParams();
+        GmxMdParams(const GmxMdParams &)            = delete;
+        GmxMdParams &operator=(const GmxMdParams &) = delete;
+        GmxMdParams(GmxMdParams &&) noexcept;
+        GmxMdParams &operator=(GmxMdParams &&) noexcept;
+
+        std::unique_ptr<GmxMdParamsImpl> params_;
+};
+
+/*!
+ * \brief A set of overloaded functions to fetch parameters of the indicated type, if possible.
+ *
+ * \param params Handle to a parameters structure from which to extract.
+ * \param name Parameter name
+ * \param (tag) type for dispatch
+ *
+ * Could be used for dispatch and/or some sort of templating in the future, but
+ * invoked directly for now.
+ */
+int extractParam(const gmxapicompat::GmxMdParams &params, const std::string &name, int);
+int64_t extractParam(const gmxapicompat::GmxMdParams& params, const std::string& name, int64_t);
+float extractParam(const gmxapicompat::GmxMdParams &params, const std::string &name, float);
+double extractParam(const gmxapicompat::GmxMdParams &params, const std::string &name, double);
+
+void setParam(gmxapicompat::GmxMdParams* params, const std::string &name, double value);
+void setParam(gmxapicompat::GmxMdParams* params, const std::string &name, int64_t value);
+// TODO: unsetParam
+
+
+// Anonymous namespace to confine helper function definitions to file scope.
+namespace
+{
+
+bool isFloat(GmxapiType dataType)
+{
+    return (dataType == GmxapiType::FLOAT64);
+}
+
+bool isInt(GmxapiType dataType)
+{
+    return (dataType == GmxapiType::INT64);
+}
+
+}      // end anonymous namespace
+
+}      // end namespace gmxapicompat
+
+#endif //GMXPY_MDPARAMS_H
index ed4a95d2b64b1bac292413aeb894492392d84a38..9dfe0f2e8824192a19dc2166c31bfaee29df4db2 100644 (file)
@@ -96,5 +96,6 @@ PYBIND11_MODULE(_gmxapi, m){
     // Get bindings exported by the various components.
     export_context(m);
     export_system(m);
+    export_tprfile(m);
 
 } // end pybind11 module
index 62c3129e91f449c9f06bdb141a9571b214d80b82..f25204eeefd7e7967a9150a8bf2e1d730398ba24 100644 (file)
@@ -41,6 +41,7 @@
  * \brief Declares symbols to be exported to gmxapi._gmxapi Python module.
  *
  * Declares namespace gmxpy, used internally in the C++ extension.
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
  * \ingroup module_python
  */
 #ifndef GMXPY_MODULE_H
@@ -89,6 +90,13 @@ namespace detail
 void export_context(pybind11::module &m);
 void export_exceptions(pybind11::module &m);
 void export_system(pybind11::module &m);
+void export_tprfile(pybind11::module &module);
+
+// Forward declaration for the module initialization.
+// TODO: is there a better way to avoid the warning generated by the pybind macro?
+// e.g. no previous prototype for function 'PyInit__gmxapi' [-Wmissing-prototypes]
+extern "C" __attribute__((visibility("default"))) PyObject* PyInit__gmxapi();
+void pybind11_init__gmxapi(pybind11::module &m);
 
 }      // end namespace gmxpy::detail
 
diff --git a/python_packaging/src/gmxapi/simulation/__init__.py b/python_packaging/src/gmxapi/simulation/__init__.py
new file mode 100644 (file)
index 0000000..18ad0c3
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2019, by the GROMACS development team, led by
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
+#
+# GROMACS is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+#
+# GROMACS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with GROMACS; if not, see
+# http://www.gnu.org/licenses, or write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+#
+# If you want to redistribute modifications to GROMACS, please
+# consider that scientific software is very special. Version
+# control is crucial - bugs must be traceable. We will be happy to
+# consider code for inclusion in the official distribution, but
+# derived work must not be called official GROMACS. Details are found
+# in the README & COPYING files - if they are missing, get the
+# official version at http://www.gromacs.org.
+#
+# To help us fund GROMACS development, we humbly ask that you cite
+# the research papers on the package. Check out http://www.gromacs.org.
diff --git a/python_packaging/src/gmxapi/simulation/fileio.py b/python_packaging/src/gmxapi/simulation/fileio.py
new file mode 100644 (file)
index 0000000..6d6f8ae
--- /dev/null
@@ -0,0 +1,275 @@
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2019, by the GROMACS development team, led by
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
+#
+# GROMACS is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+#
+# GROMACS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with GROMACS; if not, see
+# http://www.gnu.org/licenses, or write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+#
+# If you want to redistribute modifications to GROMACS, please
+# consider that scientific software is very special. Version
+# control is crucial - bugs must be traceable. We will be happy to
+# consider code for inclusion in the official distribution, but
+# derived work must not be called official GROMACS. Details are found
+# in the README & COPYING files - if they are missing, get the
+# official version at http://www.gromacs.org.
+#
+# To help us fund GROMACS development, we humbly ask that you cite
+# the research papers on the package. Check out http://www.gromacs.org.
+
+"""Provide the high-level interface to the file i/o behaviors in the package.
+"""
+# The submodule is named "fileio" instead of "io" to avoid a
+# namespace collision with a standard Python module on the default path.
+
+import typing
+
+__all__ = ['TprFile', 'read_tpr', 'write_tpr_file']
+
+import os
+
+from gmxapi import exceptions
+from gmxapi import _gmxapi
+
+
+class TprFile(object):
+    """Handle to a GROMACS simulation run input file.
+
+    TprFile objects do not have a public interface. The class is used internally
+    to manage simulation input data structures.
+
+    Attributes:
+        filename (str): Name of the file with which the object was initialized.
+        mode: File access mode from object creation.
+
+    """
+
+    def __init__(self, filename: str = None, mode: str = 'r'):
+        """Open a TPR file.
+
+        File access mode is indicated by 'r' for read-only access.
+
+        Args:
+            filename (str): Path to a run input file (e.g. 'myfile.tpr')
+            mode (str): File access mode.
+
+        Note:
+            Currently, TPR files are read-only from the Python interface.
+
+        Example:
+
+            >>> import gmxapi as gmx
+            >>> filehandle = gmx.TprFile(filename, 'r')
+
+        """
+        if filename is None:
+            raise exceptions.UsageError("TprFile objects must be associated with a file.")
+        if mode != 'r':
+            raise exceptions.UsageError("TPR files only support read-only access.")
+        self.mode = mode
+        self.filename = filename
+        self._tprFileHandle = None
+
+    def close(self):
+        # self._tprFileHandle.close()
+        self._tprFileHandle = None
+
+    def __repr__(self):
+        return "gmx.fileio.TprFile('{}', '{}')".format(self.filename, self.mode)
+
+    def __enter__(self):
+        self._tprFileHandle = _gmxapi.read_tprfile(self.filename)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.close()
+        return
+
+
+class _NodeOutput(object):
+    """Implement the `output` attribute of a simulation input node.
+
+    Attributes:
+        parameters: Simulation parameters for (re)written TPR file.
+        structure: Atomic details (not yet implemented)
+        topology: Molecular force field details (not yet implemented)
+        state: Simulation state information (not yet implemented)
+
+    """
+
+    def __init__(self, parameters=None, structure=None, topology=None, state=None):
+        """Initialize getters for output ports."""
+        self.__tprfile = parameters
+
+    @property
+    def parameters(self):
+        with self.__tprfile as fh:
+            params = fh._tprFileHandle.params()
+        return params
+
+    @property
+    def structure(self):
+        raise exceptions.ApiError("property not implemented.")
+
+    @property
+    def topology(self):
+        raise exceptions.ApiError("property not implemented.")
+
+    @property
+    def state(self):
+        raise exceptions.ApiError("property not implemented.")
+
+
+class _SimulationInput(object):
+    """
+    Simulation input interface for a TPR file read by gmx.fileio.read_tpr()
+
+    Attributes:
+        parameters: Simulation parameters for (re)written TPR file.
+        structure: Atomic details (not yet implemented)
+        topology: Molecular force field details (not yet implemented)
+        state: Simulation state information (not yet implemented)
+
+    """
+
+    def __init__(self, tprfile: typing.Union[str, TprFile]):
+        if not isinstance(tprfile, TprFile):
+            try:
+                tprfile = TprFile(tprfile)
+            except Exception as e:
+                # This class is an implementation detail of TPR file I/O...
+                raise exceptions.ApiError("Must be initialized from a TprFile.") from e
+        assert isinstance(tprfile, TprFile)
+        self.__tprfile = tprfile
+        self.__parameters = None
+
+    @property
+    def parameters(self):
+        if self.__parameters is None:
+            with self.__tprfile as fh:
+                self.__parameters = fh._tprFileHandle.params()
+        return self.__parameters
+
+    @property
+    def structure(self):
+        raise exceptions.ApiError("property not implemented.")
+
+    @property
+    def topology(self):
+        raise exceptions.ApiError("property not implemented.")
+
+    @property
+    def state(self):
+        raise exceptions.ApiError("property not implemented.")
+
+
+def read_tpr(tprfile: typing.Union[str, TprFile]):
+    """
+    Get a simulation input object from a TPR run input file.
+
+    Arguments:
+        tprfile : TPR input object or filename
+
+    Returns:
+         simulation input object
+
+    The returned object may be inspected by the user. Simulation input parameters
+    may be extracted through the `parameters` attribute.
+
+    Example:
+        >>> sim_input = gmx.fileio.read_tpr(tprfile=tprfilename)
+        >>> params = sim_input.parameters.extract()
+        >>> print(params['init-step'])
+        0
+
+    Supports the `read_tpr` gmxapi work graph operation. (not yet implemented)
+    """
+    if not isinstance(tprfile, TprFile):
+        try:
+            tprfile = TprFile(os.fsencode(tprfile), mode='r')
+        except Exception as e:
+            raise exceptions.UsageError("TPR object or file name is required.") from e
+
+    return _SimulationInput(tprfile)
+
+
+# In initial implementation, we extract the entire TPR file contents through the
+# TPR-backed GmxMdParams implementation.
+# Note: this function is not consistent with a gmxapi operation.
+def write_tpr_file(output, input=None):
+    """
+    Create a new TPR file, combining user-provided input.
+
+    .. versionadded:: 0.0.8
+        Initial version of this tool does not know how to generate a valid simulation
+        run input file from scratch, so it requires input derived from an already valid
+        TPR file.
+
+    The simulation input object should provide the gmx simulation_input interface,
+    with output ports for `parameters`, `structure`, `topology`, and `state`, such
+    as a TprFileHandle
+
+    Arguments:
+        output : TPR file name to write.
+        input : simulation input data from which to write a simulation run input file.
+
+    Use this function to write a new TPR file with data updated from an
+    existing TPR file. Keyword arguments are objects that can provide gmxapi
+    compatible access to the necessary simulation input data.
+
+    In the initial version, data must originate from an existing TPR file, and
+    only simulation parameters may be rewritten. See gmx.fileio.read_tpr()
+
+    Example:
+        >>> sim_input = gmx.fileio.read_tpr(tprfile=tprfilename)
+        >>> sim_input.parameters.set('init-step', 1)
+        >>> gmx.fileio.write_tpr_file(newfilename, input=sim_input)
+
+    Warning:
+        The interface is in flux.
+
+    TODO:
+        Be consistent about terminology for "simulation state".
+        We are currently using "simulation state" to refer both to the aggregate of
+        data (superset) necessary to launch or continue a simulation _and_ to the
+        extra data (subset) necessary to capture full program state, beyond the
+        model/method input parameters and current phase space coordinates. Maybe we
+        shouldn't expose that as a separate user-accessible object and should instead
+        make it an implementation detail of a wrapper object that has standard
+        interfaces for the non-implementation-dependent encapsulated data.
+
+    Returns:
+        TBD : possibly a completion condition of some sort and/or handle to the new File
+    """
+
+    # TODO: (Data model) Decide how to find output data sources.
+    if not hasattr(input, 'parameters'):
+        if hasattr(input, 'output'):
+            if hasattr(input.output, 'parameters'):
+                parameters = input.output.parameters
+            else:
+                raise ValueError("Need output.parameters")
+        else:
+            raise ValueError("Need output.parameters")
+    else:
+        parameters = input.parameters
+
+    if not isinstance(parameters, _gmxapi.SimulationParameters):
+        raise exceptions.TypeError(
+            "You must provide a gmx.core.SimulationParameters object to `parameters` as input.")
+    _gmxapi.write_tprfile(output, parameters)
diff --git a/python_packaging/src/gmxapi/tprfile.cpp b/python_packaging/src/gmxapi/tprfile.cpp
new file mode 100644 (file)
index 0000000..c7f8527
--- /dev/null
@@ -0,0 +1,875 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief Helper code for TPR file access.
+ *
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ * \ingroup module_python
+ */
+
+#include "tprfile.h"
+
+#include <cassert>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gromacs/mdtypes/inputrec.h"
+#include "gromacs/topology/topology.h"
+#include "gromacs/mdtypes/state.h"
+#include "gromacs/fileio/oenv.h"
+#include "gromacs/fileio/tpxio.h"
+#include "gromacs/options/timeunitmanager.h"
+#include "gromacs/utility/cstringutil.h"
+#include "gromacs/utility/programcontext.h"
+
+#include "compat_exceptions.h"
+#include "mdparams.h"
+
+namespace gmxapicompat
+{
+
+class TprContents
+{
+    public:
+        explicit TprContents(const std::string &infile) :
+            irInstance_ {std::make_unique<t_inputrec>()},
+        mtop_ {std::make_unique<gmx_mtop_t>()},
+        state_ {std::make_unique<t_state>()}
+        {
+            read_tpx_state(infile.c_str(), irInstance_.get(), state_.get(), mtop_.get());
+        }
+        ~TprContents() = default;
+        TprContents(TprContents &&source) noexcept      = default;
+        TprContents &operator=(TprContents &&) noexcept = default;
+
+        /*!
+         * \brief Get a reference to the input record in the TPR file.
+         *
+         * Note that this implementation allows different objects to share ownership
+         * of the TprFile and does not provide access restrictions to prevent multiple
+         * code blocks writing to the input record. This should be resolved with a
+         * combination of managed access controlled handles and through better
+         * management of the data structures in the TPR file. I.e. the t_inputrec is
+         * not copyable, moveable, nor default constructable (at least, to produce a
+         * valid record), and it does not necessarily make sense to map the library
+         * data structure to the file data structure (except that we don't have another
+         * way of constructing a complete and valid input record).
+         *
+         * \todo We can't play fast and loose with the irInstance for long...
+         *
+         * \return
+         */
+        t_inputrec &inputRecord() const
+        {
+            assert(irInstance_);
+            return *irInstance_;
+        }
+
+        gmx_mtop_t &molecularTopology() const
+        {
+            assert(mtop_);
+            return *mtop_;
+        }
+
+        t_state &state() const
+        {
+            assert(state_);
+            return *state_;
+        }
+    private:
+        // These types are not moveable in GROMACS 2019, so we use unique_ptr as a
+        // moveable wrapper to let TprContents be moveable.
+        std::unique_ptr<t_inputrec>        irInstance_;
+        std::unique_ptr<gmx_mtop_t>        mtop_;
+        std::unique_ptr<t_state>           state_;
+
+};
+
+// Note: This mapping is incomplete. Hopefully we can replace it before more mapping is necessary.
+// TODO: (#2993) Replace with GROMACS library header resources when available.
+const std::map<std::string, GmxapiType> simulationParameterTypeMap()
+{
+    return {
+               {
+                   "integrator", GmxapiType::STRING
+               },
+               {
+                   "tinit",      GmxapiType::FLOAT64
+               },
+               {
+                   "dt",         GmxapiType::FLOAT64
+               },
+               {
+                   "nsteps",     GmxapiType::INT64
+               },
+               {
+                   "init-step",     GmxapiType::INT64
+               },
+               {
+                   "simulation-part",     GmxapiType::INT64
+               },
+               {
+                   "comm-mode",     GmxapiType::STRING
+               },
+               {
+                   "nstcomm",     GmxapiType::INT64
+               },
+               {
+                   "comm-grps",     GmxapiType::NDARRAY
+               },                                      // Note: we do not have processing for this yet.
+               {
+                   "bd-fric",     GmxapiType::FLOAT64
+               },
+               {
+                   "ld-seed",     GmxapiType::INT64
+               },
+               {
+                   "emtol",     GmxapiType::FLOAT64
+               },
+               {
+                   "emstep",     GmxapiType::FLOAT64
+               },
+               {
+                   "niter",     GmxapiType::INT64
+               },
+               {
+                   "fcstep",     GmxapiType::FLOAT64
+               },
+               {
+                   "nstcgsteep",     GmxapiType::INT64
+               },
+               {
+                   "nbfgscorr",     GmxapiType::INT64
+               },
+               {
+                   "rtpi",     GmxapiType::FLOAT64
+               },
+               {
+                   "nstxout",     GmxapiType::INT64
+               },
+               {
+                   "nstvout",     GmxapiType::INT64
+               },
+               {
+                   "nstfout",     GmxapiType::INT64
+               },
+               {
+                   "nstlog",     GmxapiType::INT64
+               },
+               {
+                   "nstcalcenergy",     GmxapiType::INT64
+               },
+               {
+                   "nstenergy",     GmxapiType::INT64
+               },
+               {
+                   "nstxout-compressed",     GmxapiType::INT64
+               },
+               {
+                   "compressed-x-precision",     GmxapiType::FLOAT64
+               },
+               {
+                   "cutoff-scheme",     GmxapiType::STRING
+               },
+               {
+                   "nstlist",     GmxapiType::INT64
+               },
+               {
+                   "ns-type",     GmxapiType::STRING
+               },
+               {
+                   "pbc",     GmxapiType::STRING
+               },
+               {
+                   "periodic-molecules",     GmxapiType::BOOL
+               },
+//            TBD
+
+    };
+};
+
+/*
+ * Visitor for predetermined known types.
+ *
+ * Development sequence:
+ * 1. map pointers
+ * 2. map setters ()
+ * 3. template the Visitor setter for compile-time extensibility of type and to prune incompatible types.
+ * 4. switch to Variant type for handling (setter templated on caller input)
+ * 5. switch to Variant type for input as well? (Variant in public API?)
+ */
+
+const std::map<std::string, bool t_inputrec::*> boolParams()
+{
+    return {
+               {
+                   "periodic-molecules", &t_inputrec::bPeriodicMols
+               },
+//            ...
+    };
+}
+
+const std::map<std::string, int t_inputrec::*> int32Params()
+{
+    return {
+               {
+                   "simulation-part",     &t_inputrec::simulation_part
+               },
+               {
+                   "nstcomm",     &t_inputrec::nstcomm
+               },
+               {
+                   "niter",     &t_inputrec::niter
+               },
+               {
+                   "nstcgsteep",     &t_inputrec::nstcgsteep
+               },
+               {
+                   "nbfgscorr",     &t_inputrec::nbfgscorr
+               },
+               {
+                   "nstxout",     &t_inputrec::nstxout
+               },
+               {
+                   "nstvout",     &t_inputrec::nstvout
+               },
+               {
+                   "nstfout",     &t_inputrec::nstfout
+               },
+               {
+                   "nstlog",     &t_inputrec::nstlog
+               },
+               {
+                   "nstcalcenergy",     &t_inputrec::nstcalcenergy
+               },
+               {
+                   "nstenergy",     &t_inputrec::nstenergy
+               },
+               {
+                   "nstxout-compressed",     &t_inputrec::nstxout_compressed
+               },
+               {
+                   "nstlist",     &t_inputrec::nstlist
+               },
+//            ...
+    };
+}
+
+const std::map<std::string, float t_inputrec::*> float32Params()
+{
+    return {
+               {
+                   "bd-fric",     &t_inputrec::bd_fric
+               },
+               {
+                   "emtol",     &t_inputrec::em_tol
+               },
+               {
+                   "emstep",     &t_inputrec::em_stepsize
+               },
+               {
+                   "fcstep",     &t_inputrec::fc_stepsize
+               },
+               {
+                   "rtpi",     &t_inputrec::rtpi
+               },
+               {
+                   "compressed-x-precision",     &t_inputrec::x_compression_precision
+               },
+//            ...
+
+    };
+}
+const std::map<std::string, double t_inputrec::*> float64Params()
+{
+    return {
+               {
+                   "dt", &t_inputrec::delta_t
+               },
+               {
+                   "tinit", &t_inputrec::init_t
+               },
+//            ...
+
+    };
+}
+const std::map<std::string, int64_t t_inputrec::*> int64Params()
+{
+    return {
+               {
+                   "nsteps",     &t_inputrec::nsteps
+               },
+               {
+                   "init-step",     &t_inputrec::init_step
+               },
+               {
+                   "ld-seed",     &t_inputrec::ld_seed
+               },
+//            ...
+
+    };
+}
+
+/*!
+ * \brief Static mapping of parameter names to gmxapi types for GROMACS 2019.
+ *
+ * \param name MDP entry name.
+ * \return enumeration value for known parameters.
+ *
+ * \throws gmxapi_compat::ValueError for parameters with no mapping.
+ */
+GmxapiType mdParamToType(const std::string &name)
+{
+    const auto staticMap = simulationParameterTypeMap();
+    auto       entry     = staticMap.find(name);
+    if (entry == staticMap.end())
+    {
+        throw ValueError("Named parameter has unknown type mapping.");
+    }
+    return entry->second;
+};
+
+
+/*!
+ * \brief Handle / manager for GROMACS molecular computation input parameters.
+ *
+ * Interface should be consistent with MDP file entries, but data maps to TPR
+ * file interface. For type safety and simplicity, we don't have generic operator
+ * accessors. Instead, we have templated accessors that throw exceptions when
+ * there is trouble.
+ *
+ * When MDP input is entirely stored in a key-value tree, this class can be a
+ * simple adapter or wrapper. Until then, we need a manually maintained mapping
+ * of MDP entries to TPR data.
+ *
+ * Alternatively, we could update the infrastructure used by list_tpx to provide
+ * more generic output, but our efforts may be better spent in updating the
+ * infrastructure for the key-value tree input system.
+ */
+class GmxMdParamsImpl final
+{
+    public:
+        /*!
+         * \brief Create an initialized but empty parameters structure.
+         *
+         * Parameter keys are set at construction, but all values are empty. This
+         * allows the caller to check for valid parameter names or their types,
+         * while allowing the consuming code to know which parameters were explicitly
+         * set by the caller.
+         *
+         * To load values from a TPR file, see getMdParams().
+         */
+        GmxMdParamsImpl();
+
+        explicit GmxMdParamsImpl(std::shared_ptr<TprContents> tprContents);
+
+        /*!
+         * \brief Get the current list of keys.
+         *
+         * \return
+         */
+        std::vector<std::string> keys() const
+        {
+            std::vector<std::string> keyList;
+            for (auto && entry : int64Params_)
+            {
+                keyList.emplace_back(entry.first);
+            }
+            for (auto && entry : intParams_)
+            {
+                keyList.emplace_back(entry.first);
+            }
+            for (auto && entry : floatParams_)
+            {
+                keyList.emplace_back(entry.first);
+            }
+            for (auto && entry : float64Params_)
+            {
+                keyList.emplace_back(entry.first);
+            }
+            return keyList;
+        };
+
+        template<typename T> T extract(const std::string &key) const
+        {
+            auto value = T();
+            // should be an APIError
+            throw TypeError("unhandled type");
+        }
+
+        void set(const std::string &key, const int64_t &value)
+        {
+            if (int64Params_.find(key) != int64Params_.end())
+            {
+                int64Params_[key] = std::make_pair(value, true);
+
+                if (source_)
+                {
+                    auto memberPointer                    = int64Params().at(key);
+                    source_->inputRecord().*memberPointer = value;
+                }
+            }
+            else if (intParams_.find(key) != intParams_.end())
+            {
+                // TODO: check whether value is too large?
+                intParams_[key] = std::make_pair(static_cast<int>(value), true);
+
+                if (source_)
+                {
+                    auto memberPointer                    = int32Params().at(key);
+                    source_->inputRecord().*memberPointer = value;
+                }
+
+            }
+            else
+            {
+                throw KeyError("Named parameter is incompatible with integer type value.");
+            }
+        };
+
+        void set(const std::string &key, const double &value)
+        {
+            if (float64Params_.find(key) != float64Params_.end())
+            {
+                float64Params_[key] = std::make_pair(value, true);
+
+                if (source_)
+                {
+                    auto memberPointer                    = float64Params().at(key);
+                    source_->inputRecord().*memberPointer = value;
+                }
+
+            }
+            else if (floatParams_.find(key) != floatParams_.end())
+            {
+                // TODO: check whether value is too large?
+                floatParams_[key] = std::make_pair(static_cast<float>(value), true);
+
+                if (source_)
+                {
+                    auto memberPointer                    = float32Params().at(key);
+                    source_->inputRecord().*memberPointer = value;
+                }
+
+            }
+            else
+            {
+                throw KeyError("Named parameter is incompatible with floating point type value.");
+            }
+        };
+//
+//    // Uses expression SFINAE of the return type to be sure of the right overload
+//    // at template instantiation. Causes compile error if a setter is not available
+//    // for the parameter type T.
+//    template<typename T> auto set(const std::string& key, const T& value) -> decltype(setParam(key, value), void())
+//    {
+//        this->set(key, value);
+//    }
+
+        TprReadHandle getSource() const
+        {
+            // Note: might return a null handle. Need to decide what that means and how to address it.
+            return TprReadHandle(source_);
+        }
+
+    private:
+
+        // Hold the settable parameters and whether or not they have been set.
+        // TODO: update to gmxapi named types?
+        std::map < std::string, std::pair < int64_t, bool>> int64Params_;
+        std::map < std::string, std::pair < int, bool>> intParams_;
+        std::map < std::string, std::pair < float, bool>> floatParams_;
+        std::map < std::string, std::pair < double, bool>> float64Params_;
+
+        /*! \brief Shared ownership of a pack of TPR data.
+         *
+         * This is a non-normative way to retain access to gmxapi resources.
+         * \todo Subscribe to a Context-managed resource.
+         */
+        std::shared_ptr<TprContents> source_;
+};
+
+void setParam(gmxapicompat::GmxMdParams *params, const std::string &name, double value)
+{
+    assert(params != nullptr);
+    assert(params->params_ != nullptr);
+    params->params_->set(name, value);
+}
+
+void setParam(gmxapicompat::GmxMdParams *params, const std::string &name, int64_t value)
+{
+    assert(params != nullptr);
+    assert(params->params_ != nullptr);
+    params->params_->set(name, value);
+}
+
+template<typename ParamsContainerT, typename Mapping>
+static void setParam(ParamsContainerT* params, const TprContents &source, const Mapping &map)
+{
+    for (const auto &definition : map)
+    {
+        const auto &key           = definition.first;
+        auto        memberPointer = definition.second;
+        auto       &irInstance    = source.inputRecord();
+        auto        fileValue     = irInstance.*memberPointer;
+        (*params)[key] = std::make_pair(fileValue, true);
+    }
+}
+
+/*!
+ * \brief A GmxMdParams implementation that depends on TPR files.
+ *
+ * \param tprContents
+ */
+GmxMdParamsImpl::GmxMdParamsImpl(std::shared_ptr<gmxapicompat::TprContents> tprContents) :
+    source_ {std::move(tprContents)}
+{
+    if (source_)
+    {
+        setParam(&int64Params_, *source_, int64Params());
+        setParam(&intParams_, *source_, int32Params());
+        setParam(&float64Params_, *source_, float32Params());
+        setParam(&float64Params_, *source_, float64Params());
+    }
+}
+
+GmxMdParamsImpl::GmxMdParamsImpl() :
+    GmxMdParamsImpl(nullptr)
+{}
+
+template<>
+int GmxMdParamsImpl::extract<int>(const std::string &key) const
+{
+    const auto &params = intParams_;
+    const auto &entry  = params.find(key);
+    if (entry == params.cend())
+    {
+        throw KeyError("Parameter of the requested name and type not defined.");
+    }
+    else if (!entry->second.second)
+    {
+        // TODO: handle invalid and unset parameters differently.
+        throw KeyError("Parameter of the requested name not set.");
+    }
+    else
+    {
+        return entry->second.first;
+    }
+}
+
+template<>
+int64_t GmxMdParamsImpl::extract<int64_t>(const std::string &key) const
+{
+    const auto &params = int64Params_;
+    const auto &entry  = params.find(key);
+    if (entry == params.cend())
+    {
+        throw KeyError("Parameter of the requested name and type not defined.");
+    }
+    else if (!entry->second.second)
+    {
+        // TODO: handle invalid and unset parameters differently.
+        throw KeyError("Parameter of the requested name not set.");
+    }
+    else
+    {
+        return entry->second.first;
+    }
+}
+template<>
+float GmxMdParamsImpl::extract<float>(const std::string &key) const
+{
+    const auto &params = floatParams_;
+    const auto &entry  = params.find(key);
+    if (entry == params.cend())
+    {
+        throw KeyError("Parameter of the requested name and type not defined.");
+    }
+    else if (!entry->second.second)
+    {
+        // TODO: handle invalid and unset parameters differently.
+        throw KeyError("Parameter of the requested name not set.");
+    }
+    else
+    {
+        return entry->second.first;
+    }
+}
+template<>
+double GmxMdParamsImpl::extract<double>(const std::string &key) const
+{
+    const auto &params = float64Params_;
+    const auto &entry  = params.find(key);
+    if (entry == params.cend())
+    {
+        throw KeyError("Parameter of the requested name and type not defined.");
+    }
+    else if (!entry->second.second)
+    {
+        // TODO: handle invalid and unset parameters differently.
+        throw KeyError("Parameter of the requested name not set.");
+    }
+    else
+    {
+        return entry->second.first;
+    }
+}
+
+
+int extractParam(const GmxMdParams &params, const std::string &name, int)
+{
+    assert(params.params_);
+    return params.params_->extract<int>(name);
+}
+
+int64_t extractParam(const GmxMdParams &params, const std::string &name, int64_t)
+{
+    assert(params.params_);
+    int64_t value {};
+    // Allow fetching both known integer types.
+    try
+    {
+        value = params.params_->extract<int>(name);
+    }
+    catch (const KeyError &error)
+    {
+        // If not found as a regular int, check for int64.
+        try
+        {
+            value = params.params_->extract<int64_t >(name);
+        }
+        catch (const KeyError &error64)
+        {
+            throw KeyError("Parameter of the requested name not set.");
+        }
+    }
+    // Any other exceptions propagate out.
+    return value;
+}
+
+float extractParam(const GmxMdParams &params, const std::string &name, float)
+{
+    assert(params.params_);
+    return params.params_->extract<float>(name);
+}
+
+double extractParam(const GmxMdParams &params, const std::string &name, double)
+{
+    assert(params.params_);
+    double value {};
+    // Allow fetching both single and double precision.
+    try
+    {
+        value = params.params_->extract<double>(name);
+    }
+    catch (const KeyError &errorDouble)
+    {
+        // If not found as a double precision value, check for single-precision.
+        try
+        {
+            value = params.params_->extract<float>(name);
+        }
+        catch (const KeyError &errorFloat)
+        {
+            throw KeyError("Parameter of the requested name not set.");
+        }
+    }
+    // Any other exceptions propagate out.
+    return value;
+}
+
+std::vector<std::string> keys(const GmxMdParams &params)
+{
+    return params.params_->keys();
+}
+
+
+TprReadHandle readTprFile(const std::string &filename)
+{
+    auto tprfile = gmxapicompat::TprContents(filename);
+    auto handle  = gmxapicompat::TprReadHandle(std::move(tprfile));
+    return handle;
+}
+
+GmxMdParams getMdParams(const TprReadHandle &handle)
+{
+    auto tprfile = handle.get();
+    // TODO: convert to exception / decide whether null handles are allowed.
+    assert(tprfile);
+    GmxMdParams params;
+    params.params_ = std::make_unique<GmxMdParamsImpl>(tprfile);
+    return params;
+}
+
+TopologySource getTopologySource(const TprReadHandle &handle)
+{
+    TopologySource source;
+    source.tprFile_ = handle.get();
+    return source;
+}
+
+SimulationState getSimulationState(const TprReadHandle &handle)
+{
+    SimulationState source;
+    source.tprFile_ = handle.get();
+    return source;
+}
+
+StructureSource getStructureSource(const TprReadHandle &handle)
+{
+    StructureSource source;
+    source.tprFile_ = handle.get();
+    return source;
+}
+
+TprReadHandle::TprReadHandle(std::shared_ptr<TprContents> tprFile) :
+    tprContents_ {std::move(tprFile)}
+{
+}
+
+TprReadHandle getSourceFileHandle(const GmxMdParams &params)
+{
+    return params.params_->getSource();
+}
+
+void writeTprFile(const std::string     &filename,
+                  const GmxMdParams     &params,
+                  const StructureSource &structure,
+                  const SimulationState &state,
+                  const TopologySource  &topology)
+{
+    assert(params.params_);
+    // The only way we can check for consistent input right now is to make sure
+    // it all comes from the same file.
+    if (structure.tprFile_.get() != state.tprFile_.get() ||
+        state.tprFile_.get() != topology.tprFile_.get() ||
+        topology.tprFile_.get() != params.params_->getSource().get().get() ||
+        params.params_->getSource().get().get() != structure.tprFile_.get()
+        )
+    {
+        throw ValueError("writeTprFile does not yet know how to reconcile data from different TPR file sources.");
+    }
+
+    const auto  tprFileHandle = params.params_->getSource();
+    const auto  tprFile       = tprFileHandle.get();
+    assert(tprFile);
+    const auto &inputRecord   = tprFile->inputRecord();
+    const auto &writeState    = tprFile->state();
+    const auto &writeTopology = tprFile->molecularTopology();
+    write_tpx_state(filename.c_str(), &inputRecord, &writeState, &writeTopology);
+
+}
+
+TprReadHandle::TprReadHandle(TprContents &&tprFile) :
+    TprReadHandle {std::make_shared<TprContents>(std::move(tprFile))}
+{
+}
+
+std::shared_ptr<TprContents> TprReadHandle::get() const
+{
+    return tprContents_;
+}
+
+// defaulted here to delay definition until after member types are defined.
+TprReadHandle::~TprReadHandle() = default;
+
+GmxMdParams::~GmxMdParams() = default;
+
+GmxMdParams::GmxMdParams() :
+    params_ {std::make_unique<GmxMdParamsImpl>()}
+{}
+
+GmxMdParams::GmxMdParams(GmxMdParams &&) noexcept = default;
+
+GmxMdParams &GmxMdParams::operator=(GmxMdParams &&) noexcept = default;
+
+} // end namespace gmxapicompat
+
+namespace gmxpy
+{
+
+// maybe this should return a handle to the new file?
+bool copy_tprfile(const gmxapicompat::TprReadHandle &input, std::string outFile)
+{
+    if (!input.get())
+    {
+        return false;
+    }
+    gmxapicompat::writeTprFile(outFile,
+                               gmxapicompat::getMdParams(input),
+                               gmxapicompat::getStructureSource(input),
+                               gmxapicompat::getSimulationState(input),
+                               gmxapicompat::getTopologySource(input));
+    return true;
+}
+
+bool rewrite_tprfile(std::string inFile, std::string outFile, double endTime)
+{
+    bool              success = false;
+
+    const char      * top_fn = inFile.c_str();
+
+    t_inputrec        irInstance;
+    gmx_mtop_t        mtop;
+    t_state           state;
+    read_tpx_state(top_fn, &irInstance, &state, &mtop);
+
+    /* set program name, command line, and default values for output options */
+    gmx_output_env_t *oenv;
+    gmx::TimeUnit     timeUnit = gmx::TimeUnit_Default;
+    bool              bView {
+        false
+    };                 // argument that says we don't want to view graphs.
+    int xvgFormat {
+        0
+    };
+    output_env_init(&oenv, gmx::getProgramContext(),
+                    static_cast<time_unit_t>(timeUnit + 1), bView, // NOLINT(misc-misplaced-widening-cast)
+                    static_cast<xvg_format_t>(xvgFormat + 1), 0);
+
+    double  run_t    = irInstance.init_step*irInstance.delta_t + irInstance.init_t;
+
+    irInstance.nsteps = static_cast<int64_t>((endTime - run_t) / irInstance.delta_t + 0.5);
+
+    write_tpx_state(outFile.c_str(), &irInstance, &state, &mtop);
+
+    success = true;
+    return success;
+}
+
+} // end namespace gmxpy
diff --git a/python_packaging/src/gmxapi/tprfile.h b/python_packaging/src/gmxapi/tprfile.h
new file mode 100644 (file)
index 0000000..3361fa8
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief Declare TPR file helpers.
+ *
+ * \ingroup module_python
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ */
+
+#ifndef GMXPY_TPRFILE_H
+#define GMXPY_TPRFILE_H
+
+#include <string>
+#include <vector>
+
+#include "compat_exceptions.h"
+#include "mdparams.h"
+
+namespace gmxapicompat
+{
+
+/*!
+ * \brief Facade for objects that can provide atomic data for a configuration.
+ */
+class StructureSource;
+
+/*!
+ * \brief Facade for objects that can provide molecular topology information for a structure.
+ */
+class TopologySource;
+
+/*!
+ * \brief Proxy to simulation state data.
+ */
+class SimulationState;
+
+/*!
+ * \brief Manager for TPR file resources.
+ *
+ * To avoid copies, this resource-owning object is shared by consumers of its
+ * resources, even when different resources are consumed.
+ *
+ * Multiple read-only handles may be issued if there are no write-handles.
+ * One write handle may be issued if there are no other open handles.
+ *
+ * A const TprFile may only issue read file-handles, allowing handles to be
+ * issued more quickly by avoiding atomic resource locking.
+ *
+ * \note Shared ownership of file manager could be avoided if owned by a Context.
+ * It is appropriate for a Context to own and mediate access to the manager because
+ * the Context should provide the filesystem abstraction to more intelligently
+ * map named file paths to resources. For now, handles and other consumers share ownership
+ * of the TprContents manager object via shared_ptr.
+ */
+class TprContents;
+
+/*!
+ * \brief Handle for a TPR data resource.
+ *
+ * Can provide StructureSource, TopologySource, GmxMdParams, and SimulationState.
+ *
+ * This is the type of object we allow Python clients to hold references to, though
+ * we don't expose any methods to Python. Python clients should acquire access
+ * to TPR file contents with read_tpr().
+ *
+ * \todo gmxapi C++ API should provide mechanisms for subscribing to simulation
+ *       input data from various sources.
+ */
+class TprReadHandle
+{
+    public:
+        explicit TprReadHandle(std::shared_ptr<TprContents> tprFile);
+        explicit TprReadHandle(TprContents &&tprFile);
+        ~TprReadHandle();
+
+        /*!
+         * \brief Allow API functions to access data resources.
+         *
+         * Used internally. The entire TPR contents are never extracted to the
+         * client, but API implementation details need to be
+         * able to access some or all entire contents in later operations.
+         *
+         * \return Reference-counted handle to data container.
+         */
+        std::shared_ptr<TprContents> get() const;
+    private:
+        std::shared_ptr<TprContents> tprContents_;
+};
+
+/*!
+ * \brief Open a TPR file and retrieve a handle.
+ *
+ * \param filename Path of file to read.
+ * \return handle that may share ownership of TPR file resource.
+ */
+TprReadHandle readTprFile(const std::string &filename);
+
+/*!
+ * \brief Write a new TPR file to the filesystem with the provided contents.
+ *
+ * \param filename output file path
+ * \param params simulation parameters
+ * \param structure system structure (atomic configuration)
+ * \param state simulation state
+ * \param topology molecular topology
+ */
+void writeTprFile(const std::string     &filename,
+                  const GmxMdParams     &params,
+                  const StructureSource &structure,
+                  const SimulationState &state,
+                  const TopologySource  &topology);
+
+/*!
+ * \brief Helper function for early implementation.
+ *
+ * Allows extraction of TPR file information from special params objects.
+ *
+ * \todo This is a very temporary shim! Find a better way to construct simulation input.
+ */
+TprReadHandle getSourceFileHandle(const GmxMdParams &params);
+
+/*!
+ * \brief Get a topology source from the TPR contents collection.
+ * \param handle
+ * \return
+ *
+ * \todo replace with a helper template on T::topologySource() member function existence.
+ */
+
+TopologySource getTopologySource(const TprReadHandle &handle);
+
+/*!
+ * \brief Get a source of simulation state from the TPR contents collection.
+ * \param handle
+ * \return
+ *
+ * \todo template on T::simulationState() member function existence.
+ */
+SimulationState getSimulationState(const TprReadHandle &handle);
+
+/*!
+ * \brief Get a source of atomic structure from the TPR contents collection.
+ * \param handle
+ * \return
+ */
+StructureSource getStructureSource(const TprReadHandle &handle);
+
+/*!
+ * \brief Get an initialized parameters structure.
+ * \param handle
+ * \return
+ */
+GmxMdParams getMdParams(const TprReadHandle &handle);
+
+std::vector<std::string> keys(const GmxMdParams &params);
+
+class StructureSource
+{
+    public:
+        std::shared_ptr<TprContents> tprFile_;
+};
+
+class TopologySource
+{
+    public:
+        std::shared_ptr<TprContents> tprFile_;
+};
+
+class SimulationState
+{
+    public:
+        std::shared_ptr<TprContents> tprFile_;
+};
+
+} // end namespace gmxapicompat
+
+namespace gmxpy
+{
+
+/*!
+ * \brief Copy TPR file.
+ *
+ * \param input TPR source to copy from
+ * \param outFile output TPR file name
+ * \return true if successful. else false.
+ */
+bool copy_tprfile(const gmxapicompat::TprReadHandle &input, std::string outFile);
+
+/*!
+ * \brief Copy and possibly update TPR file by name.
+ *
+ * \param inFile Input file name
+ * \param outFile Output file name
+ * \param endTime Replace `nsteps` in infile with `endTime/dt`
+ * \return true if successful, else false
+ */
+bool rewrite_tprfile(std::string inFile, std::string outFile, double endTime);
+
+}      // end namespace gmxpy
+
+#endif //GMXPY_TPRFILE_H
diff --git a/python_packaging/src/gmxapi/typetemplates.h b/python_packaging/src/gmxapi/typetemplates.h
new file mode 100644 (file)
index 0000000..ec82944
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2019, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief Tools for managing mappings of gmxapi data types.
+ *
+ * \author M. Eric Irrgang <ericirrgang@gmail.com>
+ */
+
+#ifndef GMXPY_TYPETEMPLATES_H
+#define GMXPY_TYPETEMPLATES_H
+
+
+#include <type_traits>
+
+#include "mdparams.h"
+
+namespace gmxapicompat
+{
+
+namespace traits
+{
+
+// These can be more than traits. We might as well make them named types.
+struct gmxNull {
+    static const GmxapiType value = GmxapiType::gmxNull;
+};
+struct gmxMap {
+    static const GmxapiType value = GmxapiType::gmxMap;
+};
+struct gmxInt32 {
+    static const GmxapiType value = GmxapiType::gmxInt32;
+};
+struct gmxInt64 {
+    static const GmxapiType value = GmxapiType::gmxInt64;
+};
+struct gmxFloat32 {
+    static const GmxapiType value = GmxapiType::gmxFloat32;
+};
+struct gmxFloat64 {
+    static const GmxapiType value = GmxapiType::gmxFloat64;
+};
+struct gmxBool {
+    static const GmxapiType value = GmxapiType::gmxBool;
+};
+struct gmxString {
+    static const GmxapiType value = GmxapiType::gmxString;
+};
+struct gmxMDArray {
+    static const GmxapiType value = GmxapiType::gmxMDArray;
+};
+//struct gmxFloat32Vector3 {
+//    static const GmxapiType value = GmxapiType::gmxFloat32Vector3;
+//};
+//struct gmxFloat32SquareMatrix3 {
+//    static const GmxapiType value = GmxapiType::gmxFloat32SquareMatrix3;
+//};
+
+}   // end namespace traits
+
+// Use an anonymous namespace to restrict these template definitions to file scope.
+namespace
+{
+// Partial specialization of functions is not allowed, which makes the following tedious.
+// To-do: switch to type-based logic, struct templates, etc.
+template<typename T, size_t s>
+GmxapiType mapCppType()
+{
+    return GmxapiType::gmxNull;
+}
+
+template<typename T>
+GmxapiType mapCppType()
+{
+    return mapCppType<T, sizeof(T)>();
+};
+
+template<>
+GmxapiType mapCppType<bool>()
+{
+    return GmxapiType::gmxBool;
+}
+
+template<>
+GmxapiType mapCppType<int, 4>()
+{
+    return GmxapiType::gmxInt32;
+}
+
+template<>
+GmxapiType mapCppType<int, 8>()
+{
+    return GmxapiType::gmxInt64;
+};
+
+
+template<>
+GmxapiType mapCppType<float, 4>()
+{
+    return GmxapiType::gmxFloat32;
+}
+
+template<>
+GmxapiType mapCppType<double, 8>()
+{
+    return GmxapiType::gmxFloat64;
+};
+
+}      // end anonymous namespace
+
+}      // end namespace gmxapicompat
+
+#endif //GMXPY_TYPETEMPLATES_H
diff --git a/python_packaging/src/test/test_fileio.py b/python_packaging/src/test/test_fileio.py
new file mode 100644 (file)
index 0000000..3dd0536
--- /dev/null
@@ -0,0 +1,116 @@
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2019, by the GROMACS development team, led by
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
+#
+# GROMACS is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+#
+# GROMACS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with GROMACS; if not, see
+# http://www.gnu.org/licenses, or write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+#
+# If you want to redistribute modifications to GROMACS, please
+# consider that scientific software is very special. Version
+# control is crucial - bugs must be traceable. We will be happy to
+# consider code for inclusion in the official distribution, but
+# derived work must not be called official GROMACS. Details are found
+# in the README & COPYING files - if they are missing, get the
+# official version at http://www.gromacs.org.
+#
+# To help us fund GROMACS development, we humbly ask that you cite
+# the research papers on the package. Check out http://www.gromacs.org.
+
+"""Test gmx.fileio submodule"""
+import os
+import tempfile
+
+import gmxapi
+import pytest
+from gmxapi.simulation.fileio import TprFile
+from gmxapi.simulation.fileio import read_tpr
+from gmxapi.exceptions import UsageError
+
+
+@pytest.mark.usefixtures('cleandir')
+def test_tprfile_read_old(spc_water_box):
+    tpr_filename = spc_water_box
+    with pytest.raises(UsageError):
+        TprFile(tpr_filename, 'x')
+    with pytest.raises(UsageError):
+        TprFile()
+    tprfile = TprFile(tpr_filename, 'r')
+    with tprfile as fh:
+        cpp_object = fh._tprFileHandle
+        assert cpp_object is not None
+        params = cpp_object.params().extract()
+        assert "nsteps" in params
+        assert "foo" not in params
+
+
+@pytest.mark.usefixtures('cleandir')
+def test_core_tprcopy_alt(spc_water_box):
+    """Test gmx.core.copy_tprfile() for update of end_time.
+
+    Set a new end time that is 5000 steps later than the original. Read dt
+    from file to avoid floating point round-off errors.
+
+    Transitively test gmx.fileio.read_tpr()
+    """
+    tpr_filename = spc_water_box
+    additional_steps = 5000
+    sim_input = read_tpr(tpr_filename)
+    params = sim_input.parameters.extract()
+    dt = params['dt']
+    nsteps = params['nsteps']
+    init_step = params['init-step']
+    initial_endtime = (init_step + nsteps) * dt
+    new_endtime = initial_endtime + additional_steps*dt
+    _, temp_filename = tempfile.mkstemp(suffix='.tpr')
+    gmxapi._gmxapi.rewrite_tprfile(source=tpr_filename, destination=temp_filename, end_time=new_endtime)
+    tprfile = TprFile(temp_filename, 'r')
+    with tprfile as fh:
+        params = read_tpr(fh).parameters.extract()
+        dt = params['dt']
+        nsteps = params['nsteps']
+        init_step = params['init-step']
+        assert (init_step + nsteps) * dt == new_endtime
+
+    os.unlink(temp_filename)
+
+
+@pytest.mark.usefixtures('cleandir')
+def test_write_tpr_file(spc_water_box):
+    """Test gmx.fileio.write_tpr_file() using gmx.core API.
+    """
+    tpr_filename = spc_water_box
+    additional_steps = 5000
+    sim_input = read_tpr(tpr_filename)
+    params = sim_input.parameters.extract()
+    nsteps = params['nsteps']
+    init_step = params['init-step']
+    new_nsteps = init_step + additional_steps
+
+    sim_input.parameters.set('nsteps', new_nsteps)
+
+    _, temp_filename = tempfile.mkstemp(suffix='.tpr')
+    gmxapi.simulation.fileio.write_tpr_file(temp_filename, input=sim_input)
+    tprfile = TprFile(temp_filename, 'r')
+    with tprfile as fh:
+        params = read_tpr(fh).parameters.extract()
+        dt = params['dt']
+        assert params['nsteps'] != nsteps
+        assert params['nsteps'] == new_nsteps
+
+    os.unlink(temp_filename)
index 4eb50201c474856f40dbf85d14092e1e5f9f8969..656ca28ab95403e10099b99d54872f4809b8fdab 100644 (file)
@@ -51,7 +51,7 @@ configure_file(include/version.h.in include/gmxapi/version.h)
 
 add_library(gmxapi SHARED
             context.cpp
-            exceptions.cpp
+            compat_exceptions.cpp
             gmxapi.cpp
             md.cpp
             mdmodule.cpp