Coverage for biobb_analysis/gromacs/gmx_check.py: 73%

119 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-14 09:14 +0000

1#!/usr/bin/env python3 

2 

3"""Module containing the GMX Check class and the command line interface.""" 

4 

5from typing import Optional 

6 

7from biobb_common.generic.biobb_object import BiobbObject 

8from biobb_common.tools import file_utils as fu 

9from biobb_common.tools.file_utils import launchlogger 

10 

11from 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 

23class 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 

317def 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 

335gmx_check.__doc__ = GMXCheck.__doc__ 

336main = GMXCheck.get_main( 

337 gmx_check, 

338 "Checks and compares GROMACS topology, trajectory or energy files.", 

339) 

340 

341if __name__ == "__main__": 

342 main() 

343 

344