Clean up some gmxapi sources and tests.
authorM. Eric Irrgang <mei2n@virginia.edu>
Thu, 21 Oct 2021 08:19:21 +0000 (08:19 +0000)
committerJoe Jordan <ejjordan12@gmail.com>
Thu, 21 Oct 2021 08:19:21 +0000 (08:19 +0000)
python_packaging/src/gmxapi/export_tprfile.cpp
python_packaging/src/test/conftest.py
python_packaging/src/test/test_fileio.py
python_packaging/src/test/test_fileio_low_level.py [new file with mode: 0644]

index 4f7301e2dfa0bc359d921bbd558d5e5077e63df4..54d2e3bc3cce45a412c5f84c9b2256e71b3b4dd6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2019,2020, by the GROMACS development team, led by
+ * Copyright (c) 2019,2020,2021, 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.
@@ -72,63 +72,67 @@ void detail::export_tprfile(pybind11::module& module)
     // 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://gitlab.com/gromacs/gromacs/-/issues/2993
-
-                             // We can use templates and/or tag dispatch in a more complete
-                             // future implementation.
-                             const auto& paramType = gmxapicompat::mdParamToType(key);
-                             if (paramType == GmxapiType::FLOAT64)
-                             {
-                                 dictionary[key.c_str()] = extractParam(self, key, double());
-                             }
-                             else if (paramType == GmxapiType::INT64)
-                             {
-                                 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.");
+    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://gitlab.com/gromacs/gromacs/-/issues/2993
+
+                        // We can use templates and/or tag dispatch in a more complete
+                        // future implementation.
+                        const auto& paramType = gmxapicompat::mdParamToType(key);
+                        if (paramType == GmxapiType::FLOAT64)
+                        {
+                            dictionary[key.c_str()] = extractParam(self, key, double());
+                        }
+                        else if (paramType == GmxapiType::INT64)
+                        {
+                            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, int64_t value) {
-                     gmxapicompat::setParam(self, key, 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, double value) {
-                     gmxapicompat::setParam(self, key, 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.");
+    mdparams.def(
+            "set",
+            [](GmxMdParams* self, const std::string& key, int64_t value) {
+                gmxapicompat::setParam(self, key, 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, double value) {
+                gmxapicompat::setParam(self, key, 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");
@@ -142,36 +146,39 @@ void detail::export_tprfile(pybind11::module& module)
                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 gmxapicompat::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 gmxapicompat::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``.");
+    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 gmxapicompat::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 gmxapicompat::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
index b80c21d4392ecc6044903236b1997e44a91f458f..e78bcad0d7c1e5ff7f31f3a06d5707781e4fcd1c 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2019,2020, by the GROMACS development team, led by
+# Copyright (c) 2019,2020,2021, 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.
 import json
 import logging
 import os
+
 import pytest
 
 pytest_plugins = ('gmxapi.testsupport',)
 
-@pytest.fixture(scope='class')
-def spc_water_box(gmxcli, remove_tempdir):
-    """Provide a TPR input file for a simple simulation.
+
+@pytest.fixture(scope='session')
+def spc_water_box_collection(gmxcli, remove_tempdir):
+    """Provide a collection of simulation input items for a simple simulation.
 
     Prepare the MD input in a freshly created working directory.
+    Solvate a 5nm cubic box with spc water. Return a dictionary of the artifacts produced.
     """
     import gmxapi as gmx
     # TODO: Remove this import when the the spc_water_box fixture is migrated to gmxapi.testsupport
@@ -98,7 +101,7 @@ def spc_water_box(gmxcli, remove_tempdir):
             raise RuntimeError('solvate failed in spc_water_box testing fixture.')
 
         # Choose an exactly representable dt of 2^-9 ps (approximately 0.002)
-        dt = 2.**-9.
+        dt = 2. ** -9.
         mdp_input = [('integrator', 'md'),
                      ('dt', dt),
                      ('cutoff-scheme', 'Verlet'),
@@ -135,4 +138,18 @@ def spc_water_box(gmxcli, remove_tempdir):
 
         # TODO: more inspection of grompp errors...
         assert os.path.exists(tprfilename)
-        yield tprfilename
+        collection = {
+            'tpr_filename': tprfilename,
+            'mdp_input_filename': mdpfile,
+            'mdp_output_filename': mdout_mdp,
+            'topology_filename': solvate.output.file['-p'].result(),
+            'gro_filename': solvate.output.file['-o'].result(),
+            'mdp_input_list': mdp_input
+        }
+        yield collection
+
+
+@pytest.fixture(scope='session')
+def spc_water_box(spc_water_box_collection):
+    """Provide a TPR input file for a simple simulation."""
+    yield spc_water_box_collection['tpr_filename']
index a1a9d514acd989e5f4066e549e26cc94d4ac38a8..2618464112dd96abb7052653e4d5d9d5f9a4b90e 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2019, by the GROMACS development team, led by
+# Copyright (c) 2019,2021, 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.
@@ -72,37 +72,6 @@ def test_read_tpr(spc_water_box):
     assert tpr_source.output.parameters['nsteps'].result() == 2
 
 
-@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.
diff --git a/python_packaging/src/test/test_fileio_low_level.py b/python_packaging/src/test/test_fileio_low_level.py
new file mode 100644 (file)
index 0000000..216123d
--- /dev/null
@@ -0,0 +1,117 @@
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2021, 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 gmxapi._gmxapi file I/O routines."""
+import os
+import tempfile
+
+import pytest
+import gmxapi
+import gmxapi.simulation.fileio
+from gmxapi import _gmxapi
+
+
+@pytest.mark.usefixtures('cleandir')
+def test_core_read_tpr(spc_water_box):
+    tpr_filehandle: _gmxapi.TprFile = _gmxapi.read_tprfile(os.fsencode(spc_water_box))
+    parameters: _gmxapi.SimulationParameters = tpr_filehandle.params()
+    assert 'nsteps' in parameters.extract()
+    assert 'foo' not in parameters.extract()
+    assert parameters.extract()['nsteps'] == 2
+
+
+@pytest.mark.usefixtures('cleandir')
+def test_core_rewrite_tprfile(spc_water_box):
+    """Test _gmxapi.rewrite_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
+    tpr_filehandle: _gmxapi.TprFile = _gmxapi.read_tprfile(os.fsencode(spc_water_box))
+    params = tpr_filehandle.params().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.rewrite_tprfile(source=tpr_filename, destination=temp_filename, end_time=new_endtime)
+    tprfile = gmxapi.simulation.fileio.TprFile(temp_filename, 'r')
+    with tprfile as fh:
+        params = gmxapi.simulation.fileio.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_core_read_and_write_tpr_file(spc_water_box):
+    """Test gmx.fileio.write_tpr_file() using gmx.core API.
+    """
+    additional_steps = 5000
+
+    tpr_filehandle: _gmxapi.TprFile = _gmxapi.read_tprfile(os.fsencode(spc_water_box))
+    sim_input: _gmxapi.SimulationParameters = tpr_filehandle.params()
+    params: dict = sim_input.extract()
+    nsteps = params['nsteps']
+    init_step = params['init-step']
+
+    # Choose a new nsteps to check integer parameter setting.
+    new_nsteps = init_step + additional_steps
+    # Choose a new dt to check floating point parameter setting
+    new_dt = params['dt'] * 2.
+
+    sim_input.set('nsteps', new_nsteps)
+    sim_input.set('dt', new_dt)
+
+    _, temp_filename = tempfile.mkstemp(suffix='.tpr')
+    _gmxapi.write_tprfile(temp_filename, sim_input)
+
+    tprfile = gmxapi.simulation.fileio.TprFile(temp_filename, 'r')
+    with tprfile as fh:
+        params = gmxapi.simulation.fileio.read_tpr(fh).parameters.extract()
+        # Note that we have chosen an exactly representable dt for spc_water_box.
+        # Otherwise, we would have to use pytest.approx with a suitable tolerance.
+        assert params['dt'] == new_dt
+        assert params['nsteps'] != nsteps
+        assert params['nsteps'] == new_nsteps
+
+    os.unlink(temp_filename)