1 #!/usr/bin/env python3
2
3 """Module containing the GMX Check class and the command line interface."""
4
5 from typing import Optional
6
7 from biobb_common.generic.biobb_object import BiobbObject
8 from biobb_common.tools import file_utils as fu
9 from biobb_common.tools.file_utils import launchlogger
10
11 from biobb_analysis.gromacs.common import (
12 check_energy_path,
13 check_index_path,
14 check_input_path,
15 check_out_log_path,
16 check_traj_path,
17 get_binary_path,
18 is_valid_boolean,
19 is_valid_float,
20 )
21
22
23 class GMXCheck(BiobbObject):
24 """
25 | biobb_analysis GMXCheck
26 | Wrapper of the GROMACS check module for comparing and validating GROMACS files.
27 | `GROMACS check <http://manual.gromacs.org/current/onlinehelp/gmx-check.html>`_ reads, analyzes and compares run input, trajectory and energy files reporting potential differences and inconsistencies.
28
29 Args:
30 input_structure_path (str) (Optional): Path to the first GROMACS run input file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/topology.tpr>`_. Accepted formats: tpr (edam:format_2333), gro (edam:format_2033), g96 (edam:format_2033), pdb (edam:format_1476), brk (edam:format_2033), ent (edam:format_1476).
31 input_structure_2_path (str) (Optional): Path to the second GROMACS run input file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/topology.tpr>`_. Accepted formats: tpr (edam:format_2333), gro (edam:format_2033), g96 (edam:format_2033), pdb (edam:format_1476), brk (edam:format_2033), ent (edam:format_1476).
32 input_traj_path (str) (Optional): Path to the first GROMACS trajectory file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/trajectory.trr>`_. Accepted formats: xtc (edam:format_3875), trr (edam:format_3910), cpt (edam:format_2333), gro (edam:format_2033), g96 (edam:format_2033), pdb (edam:format_1476), tng (edam:format_3876).
33 input_traj_2_path (str) (Optional): Path to the second GROMACS trajectory file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/trajectory.trr>`_. Accepted formats: xtc (edam:format_3875), trr (edam:format_3910), cpt (edam:format_2333), gro (edam:format_2033), g96 (edam:format_2033), pdb (edam:format_1476), tng (edam:format_3876).
34 input_energy_path (str) (Optional): Path to the first GROMACS energy file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/energy.edr>`_. Accepted formats: edr (edam:format_2330).
35 input_energy_2_path (str) (Optional): Path to the second GROMACS energy file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/energy.edr>`_. Accepted formats: edr (edam:format_2330).
36 structure_check_path (str) (Optional): Path to the structure file to analyze for internal consistency. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/topology.tpr>`_. Accepted formats: tpr (edam:format_2333), gro (edam:format_2033), g96 (edam:format_2033), pdb (edam:format_1476), brk (edam:format_2033), ent (edam:format_1476).
37 input_index_path (str) (Optional): Path to the GROMACS index file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/gromacs/index.ndx>`_. Accepted formats: ndx (edam:format_2033).
38 output_log_path (str): Path to the text file storing the gmx check console output. File type: output. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/reference/gromacs/ref_check.log>`_. Accepted formats: txt (edam:format_2330), log (edam:format_2330), out (edam:format_2330).
39 properties (dic - Python dictionary object containing the tool parameters, not input/output files):
40 * **vdwfac** (*float*) - (0.8) Fraction of the sum of Van der Waals radii used as warning cutoff.
41 * **bonlo** (*float*) - (0.4) Minimum fraction of the sum of Van der Waals radii for bonded atoms.
42 * **bonhi** (*float*) - (0.7) Maximum fraction of the sum of Van der Waals radii for bonded atoms.
43 * **relative_tolerance** (*float*) - (0.001) Relative tolerance for comparing real values.
44 * **absolute_tolerance** (*float*) - (0.001) Absolute tolerance, useful when sums are close to zero.
45 * **rmsd** (*bool*) - (False) Print RMSD for coordinates, velocities and forces.
46 * **compare_ab** (*bool*) - (False) Compare the A and B topologies from a single input file.
47 * **last_energy_term** (*str*) - (None) Last energy term to compare.
48 * **binary_path** (*str*) - ("gmx") Path to the GROMACS executable binary.
49 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
50 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
51 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
52 * **container_path** (*str*) - (None) Container path definition.
53 * **container_image** (*str*) - ('gromacs/gromacs:2022.2') Container image definition.
54 * **container_volume_path** (*str*) - ('/tmp') Container volume path definition.
55 * **container_working_dir** (*str*) - (None) Container working directory definition.
56 * **container_user_id** (*str*) - (None) Container user_id definition.
57 * **container_shell_path** (*str*) - ('/bin/bash') Path to default shell inside the container.
58
59 Examples:
60 This is a use example of how to use the building block from Python::
61
62 from biobb_analysis.gromacs.gmx_check import gmx_check
63 prop = {}
64 gmx_check(
65 input_structure_path='/path/to/myTopology.tpr',
66 input_structure_2_path='/path/to/myTopologyCopy.tpr',
67 output_log_path='/path/to/check.log',
68 properties=prop
69 )
70
71 Info:
72 * wrapped_software:
73 * name: GROMACS check
74 * version: >=2024.5
75 * license: LGPL 2.1
76 * ontology:
77 * name: EDAM
78 * schema: http://edamontology.org/EDAM.owl
79
80 """
81
82 def __init__(
83 self,
84 input_structure_path=None,
85 input_structure_2_path=None,
86 input_traj_path=None,
87 input_traj_2_path=None,
88 input_energy_path=None,
89 input_energy_2_path=None,
90 structure_check_path=None,
91 input_index_path=None,
92 output_log_path=None,
93 properties=None,
94 **kwargs,
95 ) -> None:
96 properties = properties or {}
97
98 # Call parent class constructor
99 super().__init__(properties)
100 self.locals_var_dict = locals().copy()
101
102 # Input/Output files
103 self.io_dict = {
104 "in": {
105 "input_structure_path": input_structure_path,
106 "input_structure_2_path": input_structure_2_path,
107 "input_traj_path": input_traj_path,
108 "input_traj_2_path": input_traj_2_path,
109 "input_energy_path": input_energy_path,
110 "input_energy_2_path": input_energy_2_path,
111 "structure_check_path": structure_check_path,
112 "input_index_path": input_index_path,
113 },
114 "out": {"output_log_path": output_log_path},
115 }
116
117 # Properties specific for BB
118 self.vdwfac = properties.get("vdwfac")
119 self.bonlo = properties.get("bonlo")
120 self.bonhi = properties.get("bonhi")
121 self.relative_tolerance = properties.get("relative_tolerance")
122 self.absolute_tolerance = properties.get("absolute_tolerance")
123 self.rmsd = properties.get("rmsd", False)
124 self.compare_ab = properties.get("compare_ab", False)
125 self.last_energy_term = properties.get("last_energy_term")
126 self.properties = properties
127
128 # Properties common in all GROMACS BB
129 self.binary_path = get_binary_path(properties, "binary_path")
130
131 # Check the properties
132 self.check_init(properties)
133
134 def _validate_float_property(self, prop_name, default_value=None):
135 """Validates a float property and converts it to string."""
136 value = self.properties.get(prop_name, default_value)
137 if value is None:
138 return None
139
140 if not is_valid_float(value):
141 fu.log(
142 f"{self.__class__.__name__}: Incorrect {prop_name} provided, exiting",
143 self.out_log,
144 )
145 raise SystemExit(f"{self.__class__.__name__}: Incorrect {prop_name} provided")
146 return str(value)
147
148 def _validate_boolean_property(self, prop_name, default_value=False):
149 """Validates a boolean property."""
150 value = self.properties.get(prop_name, default_value)
151 if not is_valid_boolean(value):
152 fu.log(
153 f"{self.__class__.__name__}: Incorrect {prop_name} provided, exiting",
154 self.out_log,
155 )
156 raise SystemExit(f"{self.__class__.__name__}: Incorrect {prop_name} provided")
157 return value
158
159 def check_data_params(self, out_log, err_log):
160 """Checks all the input/output paths and parameters"""
161 input_values = [
162 self.io_dict["in"].get("input_structure_path"),
163 self.io_dict["in"].get("input_structure_2_path"),
164 self.io_dict["in"].get("input_traj_path"),
165 self.io_dict["in"].get("input_traj_2_path"),
166 self.io_dict["in"].get("input_energy_path"),
167 self.io_dict["in"].get("input_energy_2_path"),
168 self.io_dict["in"].get("structure_check_path"),
169 self.io_dict["in"].get("input_index_path"),
170 ]
171
172 if not any(input_values):
173 fu.log(
174 f"{self.__class__.__name__}: No input files provided, exiting",
175 out_log,
176 )
177 raise SystemExit(f"{self.__class__.__name__}: No input files provided")
178
179 if self.io_dict["in"].get("input_structure_path"):
180 self.io_dict["in"]["input_structure_path"] = check_input_path(
181 self.io_dict["in"]["input_structure_path"], out_log, self.__class__.__name__
182 )
183
184 if self.io_dict["in"].get("input_structure_2_path"):
185 self.io_dict["in"]["input_structure_2_path"] = check_input_path(
186 self.io_dict["in"]["input_structure_2_path"], out_log, self.__class__.__name__
187 )
188
189 if self.io_dict["in"].get("input_traj_path"):
190 self.io_dict["in"]["input_traj_path"] = check_traj_path(
191 self.io_dict["in"]["input_traj_path"], out_log, self.__class__.__name__
192 )
193
194 if self.io_dict["in"].get("input_traj_2_path"):
195 self.io_dict["in"]["input_traj_2_path"] = check_traj_path(
196 self.io_dict["in"]["input_traj_2_path"], out_log, self.__class__.__name__
197 )
198
199 if self.io_dict["in"].get("input_energy_path"):
200 self.io_dict["in"]["input_energy_path"] = check_energy_path(
201 self.io_dict["in"]["input_energy_path"], out_log, self.__class__.__name__
202 )
203
204 if self.io_dict["in"].get("input_energy_2_path"):
205 self.io_dict["in"]["input_energy_2_path"] = check_energy_path(
206 self.io_dict["in"]["input_energy_2_path"], out_log, self.__class__.__name__
207 )
208
209 if self.io_dict["in"].get("structure_check_path"):
210 self.io_dict["in"]["structure_check_path"] = check_input_path(
211 self.io_dict["in"]["structure_check_path"], out_log, self.__class__.__name__
212 )
213
214 if self.io_dict["in"].get("input_index_path"):
215 self.io_dict["in"]["input_index_path"] = check_index_path(
216 self.io_dict["in"]["input_index_path"], out_log, self.__class__.__name__
217 )
218
219 if not self.io_dict["out"].get("output_log_path"):
220 fu.log(
221 f"{self.__class__.__name__}: No output log path provided, exiting",
222 out_log,
223 )
224 raise SystemExit(f"{self.__class__.__name__}: No output log path provided")
225 self.io_dict["out"]["output_log_path"] = check_out_log_path(
226 self.io_dict["out"]["output_log_path"], out_log, self.__class__.__name__
227 )
228
229 self.vdwfac = self._validate_float_property("vdwfac", self.vdwfac)
230 self.bonlo = self._validate_float_property("bonlo", self.bonlo)
231 self.bonhi = self._validate_float_property("bonhi", self.bonhi)
232 self.relative_tolerance = self._validate_float_property(
233 "relative_tolerance", self.relative_tolerance
234 )
235 self.absolute_tolerance = self._validate_float_property(
236 "absolute_tolerance", self.absolute_tolerance
237 )
238 self.rmsd = self._validate_boolean_property("rmsd", self.rmsd)
239 self.compare_ab = self._validate_boolean_property("compare_ab", self.compare_ab)
240
241 @launchlogger
242 def launch(self) -> int:
243 """Execute the :class:`GMXCheck <gromacs.gmx_check.GMXCheck>` object."""
244
245 # check input/output paths and parameters
246 self.check_data_params(self.out_log, self.err_log)
247
248 # Setup Biobb
249 if self.check_restart():
250 return 0
251 self.stage_files()
252
253 self.cmd = [self.binary_path, "check"]
254
255 if self.stage_io_dict["in"].get("input_traj_path"):
256 self.cmd.extend(["-f", self.stage_io_dict["in"]["input_traj_path"]])
257
258 if self.stage_io_dict["in"].get("input_traj_2_path"):
259 self.cmd.extend(["-f2", self.stage_io_dict["in"]["input_traj_2_path"]])
260
261 if self.stage_io_dict["in"].get("input_structure_path"):
262 self.cmd.extend(["-s1", self.stage_io_dict["in"]["input_structure_path"]])
263
264 if self.stage_io_dict["in"].get("input_structure_2_path"):
265 self.cmd.extend(["-s2", self.stage_io_dict["in"]["input_structure_2_path"]])
266
267 if self.stage_io_dict["in"].get("structure_check_path"):
268 self.cmd.extend(["-c", self.stage_io_dict["in"]["structure_check_path"]])
269
270 if self.stage_io_dict["in"].get("input_energy_path"):
271 self.cmd.extend(["-e", self.stage_io_dict["in"]["input_energy_path"]])
272
273 if self.stage_io_dict["in"].get("input_energy_2_path"):
274 self.cmd.extend(["-e2", self.stage_io_dict["in"]["input_energy_2_path"]])
275
276 if self.stage_io_dict["in"].get("input_index_path"):
277 self.cmd.extend(["-n", self.stage_io_dict["in"]["input_index_path"]])
278
279 if self.vdwfac is not None:
280 self.cmd.extend(["-vdwfac", self.vdwfac])
281
282 if self.bonlo is not None:
283 self.cmd.extend(["-bonlo", self.bonlo])
284
285 if self.bonhi is not None:
286 self.cmd.extend(["-bonhi", self.bonhi])
287
288 if self.relative_tolerance is not None:
289 self.cmd.extend(["-tol", self.relative_tolerance])
290
291 if self.absolute_tolerance is not None:
292 self.cmd.extend(["-abstol", self.absolute_tolerance])
293
294 if self.rmsd:
295 self.cmd.append("-rmsd")
296
297 if self.compare_ab:
298 self.cmd.append("-ab")
299
300 if self.last_energy_term:
301 self.cmd.extend(["-lastener", self.last_energy_term])
302
303 self.cmd.extend(
304 [">", self.stage_io_dict["out"]["output_log_path"]],
305 )
306
307 # Run Biobb block
308 self.run_biobb()
309 # Copy files to host
310 self.copy_to_host()
311 self.remove_tmp_files()
312 self.check_arguments(output_files_created=True, raise_exception=False)
313
314 return self.return_code
315
316
317 def gmx_check(
318 input_structure_path: Optional[str] = None,
319 input_structure_2_path: Optional[str] = None,
320 input_traj_path: Optional[str] = None,
321 input_traj_2_path: Optional[str] = None,
322 input_energy_path: Optional[str] = None,
323 input_energy_2_path: Optional[str] = None,
324 structure_check_path: Optional[str] = None,
325 input_index_path: Optional[str] = None,
326 output_log_path: Optional[str] = None,
327 properties: Optional[dict] = None,
328 **kwargs,
329 ) -> int:
330 """Execute the :class:`GMXCheck <gromacs.gmx_check.GMXCheck>` class and
331 execute the :meth:`launch() <gromacs.gmx_check.GMXCheck.launch>` method."""
332 return GMXCheck(**dict(locals())).launch()
333
334
335 gmx_check.__doc__ = GMXCheck.__doc__
336 main = GMXCheck.get_main(
337 gmx_check,
338 "Checks and compares GROMACS topology, trajectory or energy files.",
339 )
340
341 if __name__ == "__main__":
342 main()