Coverage for biobb_pmx/pmxbiobb/pmxgentop.py: 73%

97 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-23 10:10 +0000

1#!/usr/bin/env python3 

2 

3"""Module containing the PMX gentop class and the command line interface.""" 

4 

5import argparse 

6import os 

7import shutil 

8import sys 

9from pathlib import Path 

10from typing import Optional 

11 

12from biobb_common.configuration import settings 

13from biobb_common.generic.biobb_object import BiobbObject 

14from biobb_common.tools import file_utils as fu 

15from biobb_common.tools.file_utils import launchlogger 

16 

17 

18class Pmxgentop(BiobbObject): 

19 """ 

20 | biobb_pmx Pmxgentop 

21 | Wrapper class for the `PMX gentop <https://github.com/deGrootLab/pmx>`_ module. 

22 | Generate a topology file for a morphing simulation. 

23 

24 Args: 

25 input_top_zip_path (str): Path the input GROMACS topology TOP and ITP files in zip format. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/topology.zip>`_. Accepted formats: zip (edam:format_3987). 

26 output_top_zip_path (str): Path the output TOP topology in zip format. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_output_topology.zip>`_. Accepted formats: zip (edam:format_3987). 

27 properties (dic): 

28 * **force_field** (*str*) - ("amber99sb-star-ildn-mut") Force field to use. If **input_top_zip_path** is a top file, it's not necessary to specify the forcefield, as it will be determined automatically. If **input_top_zip_path** is an itp file, then it's needed. 

29 * **split** (*bool*) - (False) Write separate topologies for the vdW and charge transformations. 

30 * **scale_mass** (*bool*) - (False) Scale the masses of morphing atoms so that dummies have a mass of 1. 

31 * **gmx_lib** (*str*) - ("$CONDA_PREFIX/lib/python3.7/site-packages/pmx/data/mutff/") Path to the GMXLIB folder in your computer. 

32 * **binary_path** (*str*) - ("pmx") Path to the PMX command line interface. 

33 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files. 

34 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist. 

35 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory. 

36 * **container_path** (*str*) - (None) Path to the binary executable of your container. 

37 * **container_image** (*str*) - ("gromacs/gromacs:latest") Container Image identifier. 

38 * **container_volume_path** (*str*) - ("/inout") Path to an internal directory in the container. 

39 * **container_working_dir** (*str*) - (None) Path to the internal CWD in the container. 

40 * **container_user_id** (*str*) - (None) User number id to be mapped inside the container. 

41 * **container_shell_path** (*str*) - ("/bin/bash") Path to the binary executable of the container shell. 

42 

43 Examples: 

44 This is a use example of how to use the building block from Python:: 

45 

46 from biobb_pmx.pmxbiobb.pmxgentop import pmxgentop 

47 prop = { 

48 'gmx_lib': '/path/to/myGMXLIB/', 

49 'force_field': 'amber99sb-star-ildn-mut' 

50 } 

51 pmxgentop(input_top_zip_path='/path/to/myTopology.zip', 

52 output_top_zip_path='/path/to/newTopology.zip', 

53 properties=prop) 

54 

55 Info: 

56 * wrapped_software: 

57 * name: PMX gentop 

58 * version: >=1.0.1 

59 * license: GNU 

60 * ontology: 

61 * name: EDAM 

62 * schema: http://edamontology.org/EDAM.owl 

63 

64 """ 

65 

66 def __init__( 

67 self, 

68 input_top_zip_path: str, 

69 output_top_zip_path: str, 

70 properties: Optional[dict] = None, 

71 **kwargs, 

72 ) -> None: 

73 properties = properties or {} 

74 

75 # Call parent class constructor 

76 super().__init__(properties) 

77 self.locals_var_dict = locals().copy() 

78 

79 # Input/Output files 

80 self.io_dict = {"in": {}, "out": {"output_top_zip_path": output_top_zip_path}} 

81 # Should not be copied inside container 

82 self.input_top_zip_path = input_top_zip_path 

83 

84 # Properties specific for BB 

85 self.force_field = properties.get("force_field", "amber99sb-star-ildn-mut") 

86 self.split = properties.get("split", False) 

87 self.scale_mass = properties.get("scale_mass", False) 

88 

89 # Properties common in all PMX BB 

90 self.gmx_lib = properties.get("gmx_lib", None) 

91 if not self.gmx_lib and os.environ.get("CONDA_PREFIX", ""): 

92 python_version = f"{sys.version_info.major}.{sys.version_info.minor}" 

93 self.gmx_lib = str( 

94 Path(os.environ.get("CONDA_PREFIX", "")).joinpath( 

95 f"lib/python{python_version}/site-packages/pmx/data/mutff/" 

96 ) 

97 ) 

98 if properties.get("container_path"): 

99 self.gmx_lib = str( 

100 Path("/usr/local/").joinpath( 

101 "lib/python3.8/site-packages/pmx/data/mutff/" 

102 ) 

103 ) 

104 self.binary_path = properties.get("binary_path", "pmx") 

105 

106 # Check the properties 

107 self.check_properties(properties) 

108 self.check_arguments() 

109 

110 @launchlogger 

111 def launch(self) -> int: 

112 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` pmx.pmxgentop.Pmxgentop object.""" 

113 

114 # Setup Biobb 

115 if self.check_restart(): 

116 return 0 

117 self.stage_files() 

118 

119 # Check if executable exists 

120 if not self.container_path: 

121 if not Path(self.binary_path).is_file(): 

122 if not shutil.which(self.binary_path): 

123 raise FileNotFoundError( 

124 "Executable %s not found. Check if it is installed in your system and correctly defined in the properties" 

125 % self.binary_path 

126 ) 

127 

128 # Unzip topology to topology_out 

129 top_file = fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log) 

130 top_dir = str(Path(top_file).parent) 

131 

132 # Copy extra files to container: topology folder 

133 if self.container_path: 

134 fu.log("Container execution enabled", self.out_log) 

135 fu.log(f"Unique dir: {self.stage_io_dict['unique_dir']}", self.out_log) 

136 fu.log( 

137 f"{self.stage_io_dict['unique_dir']} files: {os.listdir(self.stage_io_dict['unique_dir'])}", 

138 self.out_log, 

139 ) 

140 fu.log( 

141 f"Copy all files of the unzipped original topology to unique dir: {self.out_log}" 

142 ) 

143 shutil.copytree( 

144 top_dir, 

145 str( 

146 Path(self.stage_io_dict.get("unique_dir", "")).joinpath( 

147 Path(top_dir).name 

148 ) 

149 ), 

150 ) 

151 top_file = str( 

152 Path(self.container_volume_path).joinpath( 

153 Path(top_dir).name, Path(top_file).name 

154 ) 

155 ) 

156 

157 output_file_name = fu.create_name( 

158 prefix=self.prefix, step=self.step, name=str(Path(top_file).name) 

159 ) 

160 u_dir = fu.create_unique_dir() 

161 unique_dir_output_file = str( 

162 Path(u_dir).joinpath(output_file_name) 

163 ) 

164 fu.log(f"unique_dir_output_file: {unique_dir_output_file}", self.out_log) 

165 

166 if self.container_path: 

167 fu.log("Change references for container:", self.out_log) 

168 unique_dir_output_file = str( 

169 Path(self.container_volume_path).joinpath(Path(output_file_name)) 

170 ) 

171 fu.log( 

172 f" unique_dir_output_file: {unique_dir_output_file}", self.out_log 

173 ) 

174 

175 self.cmd = [ 

176 self.binary_path, 

177 "gentop", 

178 "-o", 

179 str(Path(unique_dir_output_file)), 

180 "-ff", 

181 self.force_field, 

182 "-p", 

183 top_file, 

184 ] 

185 

186 if self.split: 

187 self.cmd.append("--split") 

188 if self.scale_mass: 

189 self.cmd.append("--scale_mass") 

190 

191 if self.gmx_lib: 

192 self.env_vars_dict["GMXLIB"] = self.gmx_lib 

193 

194 # Run Biobb block 

195 self.run_biobb() 

196 

197 # Copy files to host 

198 self.copy_to_host() 

199 

200 if self.container_path: 

201 unique_dir_output_file = str( 

202 Path(self.stage_io_dict.get("unique_dir", "")).joinpath( 

203 Path(unique_dir_output_file).name 

204 ) 

205 ) 

206 

207 # Remove paths from top file 

208 with open(Path(unique_dir_output_file)) as top_fh: 

209 top_lines = top_fh.readlines() 

210 with open(Path(unique_dir_output_file), "w") as top_fh: 

211 for line in top_lines: 

212 top_fh.write( 

213 line.replace(str(Path(unique_dir_output_file).parent) + "/", "") 

214 ) 

215 # Copy the not modified itp files 

216 for orig_itp_file in Path(top_dir).iterdir(): 

217 fu.log( 

218 f"Check if {str(Path(unique_dir_output_file).parent.joinpath(Path(orig_itp_file).name))} exists", 

219 self.out_log, 

220 self.global_log, 

221 ) 

222 if ( 

223 not Path(unique_dir_output_file) 

224 .parent.joinpath(Path(orig_itp_file).name) 

225 .exists() 

226 ): 

227 shutil.copy(orig_itp_file, Path(unique_dir_output_file).parent) 

228 fu.log( 

229 f"Copying {str(orig_itp_file)} to: {str(Path(unique_dir_output_file).parent)}", 

230 self.out_log, 

231 self.global_log, 

232 ) 

233 

234 # zip topology 

235 fu.log( 

236 "Compressing topology to: %s" % self.io_dict["out"]["output_top_zip_path"], 

237 self.out_log, 

238 self.global_log, 

239 ) 

240 fu.zip_top( 

241 zip_file=self.io_dict["out"]["output_top_zip_path"], 

242 top_file=str(Path(unique_dir_output_file)), 

243 out_log=self.out_log, 

244 ) 

245 

246 self.tmp_files.extend([ 

247 # self.stage_io_dict.get("unique_dir", ""), 

248 top_dir, 

249 u_dir 

250 ]) 

251 self.remove_tmp_files() 

252 

253 self.check_arguments(output_files_created=True, raise_exception=False) 

254 return self.return_code 

255 

256 

257def pmxgentop( 

258 input_top_zip_path: str, 

259 output_top_zip_path: str, 

260 properties: Optional[dict] = None, 

261 **kwargs, 

262) -> int: 

263 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` class and 

264 execute the :meth:`launch() <pmx.pmxgentop.Pmxgentop.launch> method.""" 

265 

266 return Pmxgentop( 

267 input_top_zip_path=input_top_zip_path, 

268 output_top_zip_path=output_top_zip_path, 

269 properties=properties, 

270 **kwargs, 

271 ).launch() 

272 

273 pmxgentop.__doc__ = Pmxgentop.__doc__ 

274 

275 

276def main(): 

277 """Command line execution of this building block. Please check the command line documentation.""" 

278 parser = argparse.ArgumentParser( 

279 description="Wrapper class for the PMX gentop module", 

280 formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999), 

281 ) 

282 parser.add_argument( 

283 "-c", 

284 "--config", 

285 required=False, 

286 help="This file can be a YAML file, JSON file or JSON string", 

287 ) 

288 

289 # Specific args of each building block 

290 required_args = parser.add_argument_group("required arguments") 

291 required_args.add_argument( 

292 "--input_top_zip_path", 

293 required=True, 

294 help="Path to the input topology zip file", 

295 ) 

296 required_args.add_argument( 

297 "--output_top_zip_path", 

298 required=True, 

299 help="Path to the output topology zip file", 

300 ) 

301 

302 args = parser.parse_args() 

303 config = args.config if args.config else None 

304 properties = settings.ConfReader(config=config).get_prop_dic() 

305 

306 # Specific call of each building block 

307 pmxgentop( 

308 input_top_zip_path=args.input_top_zip_path, 

309 output_top_zip_path=args.output_top_zip_path, 

310 properties=properties, 

311 ) 

312 

313 

314if __name__ == "__main__": 

315 main()