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

86 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-22 17:32 +0000

1#!/usr/bin/env python3 

2 

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

4 

5import os 

6import shutil 

7import sys 

8from pathlib import Path 

9from typing import Optional 

10 

11from biobb_common.generic.biobb_object import BiobbObject 

12from biobb_common.tools import file_utils as fu 

13from biobb_common.tools.file_utils import launchlogger 

14 

15 

16class Pmxgentop(BiobbObject): 

17 """ 

18 | biobb_pmx Pmxgentop 

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

20 | Generate a topology file for a morphing simulation. 

21 

22 Args: 

23 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). 

24 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). 

25 properties (dic): 

26 * **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. 

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

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

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

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

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

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

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

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

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

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

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

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

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

40 

41 Examples: 

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

43 

44 from biobb_pmx.pmxbiobb.pmxgentop import pmxgentop 

45 prop = { 

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

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

48 } 

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

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

51 properties=prop) 

52 

53 Info: 

54 * wrapped_software: 

55 * name: PMX gentop 

56 * version: >=1.0.1 

57 * license: GNU 

58 * ontology: 

59 * name: EDAM 

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

61 

62 """ 

63 

64 def __init__( 

65 self, 

66 input_top_zip_path: str, 

67 output_top_zip_path: str, 

68 properties: Optional[dict] = None, 

69 **kwargs, 

70 ) -> None: 

71 properties = properties or {} 

72 

73 # Call parent class constructor 

74 super().__init__(properties) 

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

76 

77 # Input/Output files 

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

79 # Should not be copied inside container 

80 self.input_top_zip_path = input_top_zip_path 

81 

82 # Properties specific for BB 

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

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

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

86 

87 # Properties common in all PMX BB 

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

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

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

91 self.gmx_lib = str( 

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

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

94 ) 

95 ) 

96 if properties.get("container_path"): 

97 self.gmx_lib = str( 

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

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

100 ) 

101 ) 

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

103 

104 # Check the properties 

105 self.check_properties(properties) 

106 self.check_arguments() 

107 

108 @launchlogger 

109 def launch(self) -> int: 

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

111 

112 # Setup Biobb 

113 if self.check_restart(): 

114 return 0 

115 self.stage_files() 

116 

117 # Check if executable exists 

118 if not self.container_path: 

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

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

121 raise FileNotFoundError( 

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

123 % self.binary_path 

124 ) 

125 

126 # Unzip topology to topology_out 

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

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

129 

130 # Copy extra files to container: topology folder 

131 if self.container_path: 

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

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

134 fu.log( 

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

136 self.out_log, 

137 ) 

138 fu.log( 

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

140 ) 

141 shutil.copytree( 

142 top_dir, 

143 str( 

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

145 Path(top_dir).name 

146 ) 

147 ), 

148 ) 

149 top_file = str( 

150 Path(self.container_volume_path).joinpath( 

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

152 ) 

153 ) 

154 

155 output_file_name = fu.create_name( 

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

157 ) 

158 u_dir = fu.create_unique_dir() 

159 unique_dir_output_file = str( 

160 Path(u_dir).joinpath(output_file_name) 

161 ) 

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

163 

164 if self.container_path: 

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

166 unique_dir_output_file = str( 

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

168 ) 

169 fu.log( 

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

171 ) 

172 

173 self.cmd = [ 

174 self.binary_path, 

175 "gentop", 

176 "-o", 

177 str(Path(unique_dir_output_file)), 

178 "-ff", 

179 self.force_field, 

180 "-p", 

181 top_file, 

182 ] 

183 

184 if self.split: 

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

186 if self.scale_mass: 

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

188 

189 if self.gmx_lib: 

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

191 

192 # Run Biobb block 

193 self.run_biobb() 

194 

195 # Copy files to host 

196 self.copy_to_host() 

197 

198 if self.container_path: 

199 unique_dir_output_file = str( 

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

201 Path(unique_dir_output_file).name 

202 ) 

203 ) 

204 

205 # Remove paths from top file 

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

207 top_lines = top_fh.readlines() 

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

209 for line in top_lines: 

210 top_fh.write( 

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

212 ) 

213 # Copy the not modified itp files 

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

215 fu.log( 

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

217 self.out_log, 

218 self.global_log, 

219 ) 

220 if ( 

221 not Path(unique_dir_output_file) 

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

223 .exists() 

224 ): 

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

226 fu.log( 

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

228 self.out_log, 

229 self.global_log, 

230 ) 

231 

232 # zip topology 

233 fu.log( 

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

235 self.out_log, 

236 self.global_log, 

237 ) 

238 fu.zip_top( 

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

240 top_file=str(Path(unique_dir_output_file)), 

241 out_log=self.out_log, 

242 remove_original_files=self.remove_tmp 

243 ) 

244 

245 self.tmp_files.extend([top_dir, u_dir]) 

246 self.remove_tmp_files() 

247 

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

249 return self.return_code 

250 

251 

252def pmxgentop( 

253 input_top_zip_path: str, 

254 output_top_zip_path: str, 

255 properties: Optional[dict] = None, 

256 **kwargs, 

257) -> int: 

258 """Create the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` class and 

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

260 return Pmxgentop(**dict(locals())).launch() 

261 

262 

263pmxgentop.__doc__ = Pmxgentop.__doc__ 

264main = Pmxgentop.get_main(pmxgentop, "Wrapper class for the PMX gentop module") 

265 

266if __name__ == "__main__": 

267 main()