From: M. Eric Irrgang Date: Thu, 21 Oct 2021 08:19:21 +0000 (+0000) Subject: Clean up some gmxapi sources and tests. X-Git-Url: http://biod.pnpi.spb.ru/gitweb/?p=alexxy%2Fgromacs.git;a=commitdiff_plain;h=1cec3d370882e309e9955e3c0361b86dd52ed606 Clean up some gmxapi sources and tests. --- diff --git a/python_packaging/src/gmxapi/export_tprfile.cpp b/python_packaging/src/gmxapi/export_tprfile.cpp index 4f7301e2df..54d2e3bc3c 100644 --- a/python_packaging/src/gmxapi/export_tprfile.cpp +++ b/python_packaging/src/gmxapi/export_tprfile.cpp @@ -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_ 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 diff --git a/python_packaging/src/test/conftest.py b/python_packaging/src/test/conftest.py index b80c21d439..e78bcad0d7 100644 --- a/python_packaging/src/test/conftest.py +++ b/python_packaging/src/test/conftest.py @@ -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. @@ -37,15 +37,18 @@ 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'] diff --git a/python_packaging/src/test/test_fileio.py b/python_packaging/src/test/test_fileio.py index a1a9d514ac..2618464112 100644 --- a/python_packaging/src/test/test_fileio.py +++ b/python_packaging/src/test/test_fileio.py @@ -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 index 0000000000..216123d8a9 --- /dev/null +++ b/python_packaging/src/test/test_fileio_low_level.py @@ -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)