Require pybind 2.6 from environment for gmxapi Python package extension module.
[alexxy/gromacs.git] / python_packaging / src / gmxapi / simulation / fileio.py
1 #
2 # This file is part of the GROMACS molecular simulation package.
3 #
4 # Copyright (c) 2019,2021, by the GROMACS development team, led by
5 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 # and including many others, as listed in the AUTHORS file in the
7 # top-level source directory and at http://www.gromacs.org.
8 #
9 # GROMACS is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public License
11 # as published by the Free Software Foundation; either version 2.1
12 # of the License, or (at your option) any later version.
13 #
14 # GROMACS is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with GROMACS; if not, see
21 # http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23 #
24 # If you want to redistribute modifications to GROMACS, please
25 # consider that scientific software is very special. Version
26 # control is crucial - bugs must be traceable. We will be happy to
27 # consider code for inclusion in the official distribution, but
28 # derived work must not be called official GROMACS. Details are found
29 # in the README & COPYING files - if they are missing, get the
30 # official version at http://www.gromacs.org.
31 #
32 # To help us fund GROMACS development, we humbly ask that you cite
33 # the research papers on the package. Check out http://www.gromacs.org.
34
35 """Provide the high-level interface to the file i/o behaviors in the package.
36 """
37 # The submodule is named "fileio" instead of "io" to avoid a
38 # namespace collision with a standard Python module on the default path.
39
40 import typing
41
42 __all__ = ['TprFile', 'read_tpr', 'write_tpr_file']
43
44 import os
45
46 from gmxapi import exceptions
47
48
49 class TprFile(object):
50     """Handle to a GROMACS simulation run input file.
51
52     TprFile objects do not have a public interface. The class is used internally
53     to manage simulation input data structures.
54
55     Attributes:
56         filename (str): Name of the file with which the object was initialized.
57         mode: File access mode from object creation.
58
59     """
60
61     def __init__(self, filename: str = None, mode: str = 'r'):
62         """Open a TPR file.
63
64         File access mode is indicated by 'r' for read-only access.
65
66         Args:
67             filename (str): Path to a run input file (e.g. 'myfile.tpr')
68             mode (str): File access mode.
69
70         Note:
71             Currently, TPR files are read-only from the Python interface.
72
73         """
74         if filename is None:
75             raise exceptions.UsageError("TprFile objects must be associated with a file.")
76         if mode != 'r':
77             raise exceptions.UsageError("TPR files only support read-only access.")
78         self.mode = mode
79         self.filename = filename
80         self._tprFileHandle = None
81
82     def close(self):
83         # self._tprFileHandle.close()
84         self._tprFileHandle = None
85
86     def __repr__(self):
87         return "{}('{}', '{}')".format(self.__class__.__name__, self.filename, self.mode)
88
89     def __enter__(self):
90         import gmxapi._gmxapi as _gmxapi
91         self._tprFileHandle = _gmxapi.read_tprfile(self.filename)
92         return self
93
94     def __exit__(self, exc_type, exc_val, exc_tb):
95         self.close()
96         return
97
98
99 class _NodeOutput(object):
100     """Implement the `output` attribute of a simulation input node.
101
102     Attributes:
103         parameters: Simulation parameters for (re)written TPR file.
104         structure: Atomic details (not yet implemented)
105         topology: Molecular force field details (not yet implemented)
106         state: Simulation state information (not yet implemented)
107
108     """
109
110     def __init__(self, parameters=None, structure=None, topology=None, state=None):
111         """Initialize getters for output ports."""
112         self.__tprfile = parameters
113
114     @property
115     def parameters(self):
116         with self.__tprfile as fh:
117             params = fh._tprFileHandle.params()
118         return params
119
120     @property
121     def structure(self):
122         raise exceptions.ApiError("property not implemented.")
123
124     @property
125     def topology(self):
126         raise exceptions.ApiError("property not implemented.")
127
128     @property
129     def state(self):
130         raise exceptions.ApiError("property not implemented.")
131
132
133 class _SimulationInput(object):
134     """
135     Simulation input interface for a TPR file read by gmx.fileio.read_tpr()
136
137     Attributes:
138         parameters: Simulation parameters for (re)written TPR file.
139         structure: Atomic details (not yet implemented)
140         topology: Molecular force field details (not yet implemented)
141         state: Simulation state information (not yet implemented)
142
143     """
144
145     def __init__(self, tprfile: typing.Union[str, TprFile]):
146         if not isinstance(tprfile, TprFile):
147             try:
148                 tprfile = TprFile(tprfile)
149             except Exception as e:
150                 # This class is an implementation detail of TPR file I/O...
151                 raise exceptions.ApiError("Must be initialized from a TprFile.") from e
152         assert isinstance(tprfile, TprFile)
153         self.__tprfile = tprfile
154         self.__parameters = None
155
156     @property
157     def parameters(self):
158         if self.__parameters is None:
159             with self.__tprfile as fh:
160                 self.__parameters = fh._tprFileHandle.params()
161         return self.__parameters
162
163     @property
164     def structure(self):
165         raise exceptions.ApiError("property not implemented.")
166
167     @property
168     def topology(self):
169         raise exceptions.ApiError("property not implemented.")
170
171     @property
172     def state(self):
173         raise exceptions.ApiError("property not implemented.")
174
175
176 def read_tpr(tprfile: typing.Union[str, TprFile]):
177     """
178     Get a simulation input object from a TPR run input file.
179
180     Arguments:
181         tprfile : TPR input object or filename
182
183     Returns:
184          simulation input object
185
186     The returned object may be inspected by the user. Simulation input parameters
187     may be extracted through the `parameters` attribute.
188
189     Example:
190         >>> sim_input = gmx.fileio.read_tpr(tprfile=tprfilename)
191         >>> params = sim_input.parameters.extract()
192         >>> print(params['init-step'])
193         0
194
195     Supports the `read_tpr` gmxapi work graph operation. (not yet implemented)
196     """
197     if not isinstance(tprfile, TprFile):
198         try:
199             tprfile = TprFile(os.fsencode(tprfile), mode='r')
200         except Exception as e:
201             raise exceptions.UsageError("TPR object or file name is required.") from e
202
203     return _SimulationInput(tprfile)
204
205
206 # In initial implementation, we extract the entire TPR file contents through the
207 # TPR-backed GmxMdParams implementation.
208 # Note: this function is not consistent with a gmxapi operation.
209 def write_tpr_file(output, input=None):
210     """
211     Create a new TPR file, combining user-provided input.
212
213     .. versionadded:: 0.0.8
214         Initial version of this tool does not know how to generate a valid simulation
215         run input file from scratch, so it requires input derived from an already valid
216         TPR file.
217
218     The simulation input object should provide the gmx simulation_input interface,
219     with output ports for `parameters`, `structure`, `topology`, and `state`, such
220     as a TprFileHandle
221
222     Arguments:
223         output : TPR file name to write.
224         input : simulation input data from which to write a simulation run input file.
225
226     Use this function to write a new TPR file with data updated from an
227     existing TPR file. Keyword arguments are objects that can provide gmxapi
228     compatible access to the necessary simulation input data.
229
230     In the initial version, data must originate from an existing TPR file, and
231     only simulation parameters may be rewritten. See gmx.fileio.read_tpr()
232
233     Example:
234         >>> sim_input = gmx.fileio.read_tpr(tprfile=tprfilename)
235         >>> sim_input.parameters.set('init-step', 1)
236         >>> gmx.fileio.write_tpr_file(newfilename, input=sim_input)
237
238     Warning:
239         The interface is in flux.
240
241     TODO:
242         Be consistent about terminology for "simulation state".
243         We are currently using "simulation state" to refer both to the aggregate of
244         data (superset) necessary to launch or continue a simulation _and_ to the
245         extra data (subset) necessary to capture full program state, beyond the
246         model/method input parameters and current phase space coordinates. Maybe we
247         shouldn't expose that as a separate user-accessible object and should instead
248         make it an implementation detail of a wrapper object that has standard
249         interfaces for the non-implementation-dependent encapsulated data.
250
251     Returns:
252         TBD : possibly a completion condition of some sort and/or handle to the new File
253     """
254     import gmxapi._gmxapi as _gmxapi
255
256     # TODO: (Data model) Decide how to find output data sources.
257     if not hasattr(input, 'parameters'):
258         if hasattr(input, 'output'):
259             if hasattr(input.output, 'parameters'):
260                 parameters = input.output.parameters
261             else:
262                 raise ValueError("Need output.parameters")
263         else:
264             raise ValueError("Need output.parameters")
265     else:
266         parameters = input.parameters
267
268     if not isinstance(parameters, _gmxapi.SimulationParameters):
269         raise exceptions.TypeError(
270             "You must provide a gmx.core.SimulationParameters object to `parameters` as input.")
271     _gmxapi.write_tprfile(output, parameters)