Coverage for biobb_pmx/pmxbiobb/pmxligand_hybrid.py: 79%

82 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 06:59 +0000

1#!/usr/bin/env python3 

2 

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

4 

5import os 

6import shutil 

7import sys 

8from pathlib import Path, PurePath 

9from typing import Optional 

10 

11from biobb_common.generic.biobb_object import BiobbObject 

12from biobb_common.tools.file_utils import launchlogger 

13 

14 

15class Pmxligand_hybrid(BiobbObject): 

16 """ 

17 | biobb_pmx Pmxligand_hybrid 

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

19 | Create a hybrid topology and structure based on two ligand structures. 

20 

21 Args: 

22 input_structure1_path (str): Path to the input ligand structure file 1. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/lig1.pdb>`_. Accepted formats: pdb (edam:format_1476). 

23 input_structure2_path (str): Path to the input ligand structure file 2. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/lig2.pdb>`_. Accepted formats: pdb (edam:format_1476). 

24 input_topology1_path (str): Path to the input ligand topology file 1. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/topoLig1.itp>`_. Accepted formats: itp (edam:format_3883). 

25 input_topology2_path (str): Path to the input ligand topology file 2. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/topoLig2.itp>`_. Accepted formats: itp (edam:format_3883). 

26 input_pairs_path (str) (Optional): Path to the input atom pair mapping. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/myPairs1.dat>`_. Accepted formats: dat (edam:format_1637), txt (edam:format_2330). 

27 input_scaffold1_path (str) (Optional): Path to the index of atoms to consider for the ligand structure 1. File type: input. Accepted formats: ndx (edam:format_2033). 

28 input_scaffold2_path (str) (Optional): Path to the index of atoms to consider for the ligand structure 2. File type: input. Accepted formats: ndx (edam:format_2033). 

29 output_log_path (str): Path to the log file. File type: output. Accepted formats: log (edam:format_2330), txt (edam:format_2330), out (edam:format_2330). 

30 output_structure1_path (str): Path to the output hybrid structure based on the ligand 1. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_hybridStructure1.pdb>`_. Accepted formats: pdb (edam:format_1476). 

31 output_structure2_path (str): Path to the output hybrid structure based on the ligand 2. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_hybridStructure2.pdb>`_. Accepted formats: pdb (edam:format_1476). 

32 output_topology_path (str): Path to the output hybrid topology. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_hybridTopology.itp>`_. Accepted formats: itp (edam:format_3883). 

33 output_atomtypes_path (str): Path to the atom types for the output hybrid topology. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_hybridAtomTypes.itp>`_. Accepted formats: itp (edam:format_3883). 

34 

35 properties (dic): 

36 * **fit** (*bool*) - (False) Fit ligand structure 1 onto ligand structure 2 (Only used if input_pairs_path is provided). 

37 * **split** (*bool*) - (False) Split the topology into separate transitions. 

38 * **scDUMm** (*float*) - (1.0) Scale dummy masses using the counterpart atoms. 

39 * **scDUMa** (*float*) - (1.0) Scale bonded dummy angle parameters. 

40 * **scDUMd** (*float*) - (1.0) Scale bonded dummy dihedral parameters. 

41 * **deAng** (*bool*) - (False) Decouple angles composed of 1 dummy and 2 non-dummies. 

42 * **distance** (*float*) - (0.05) Distance (nm) between atoms to consider them morphable for alignment approach (Only used if input_pairs_path is not provided). 

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

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

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

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

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

48 * **container_image** (*str*) - (None) Container Image identifier. 

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

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

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

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

53 

54 Examples: 

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

56 

57 from biobb_pmx.pmxbiobb.pmxligand_hybrid import pmxligand_hybrid 

58 prop = { 

59 'fit' : True, 

60 'distance': 0.05 

61 } 

62 pmxligand_hybrid(input_structure1_path='/path/to/myStructure1.pdb', 

63 input_structure2_path='/path/to/myStructure2.pdb', 

64 input_topology1_path='/path/to/myTopology1.pdb', 

65 input_topology2_path='/path/to/myTopology2.pdb', 

66 input_pairs_path='/path/to/myPairs.dat', 

67 output_log_path='/path/to/myLog.log', 

68 output_structure1_path='/path/to/myStructureOutput1.pdb', 

69 output_structure2_path='/path/to/myStructureOutput2.pdb', 

70 output_topology_path='/path/to/myTopologyOutput.pdb', 

71 output_atomtypes_path='/path/to/myAtomTypesOutput.pdb', 

72 properties=prop) 

73 

74 Info: 

75 * wrapped_software: 

76 * name: PMX ligand_hybrid 

77 * version: >=1.0.1 

78 * license: GNU 

79 * ontology: 

80 * name: EDAM 

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

82 

83 """ 

84 

85 def __init__( 

86 self, 

87 input_structure1_path: str, 

88 input_structure2_path: str, 

89 input_topology1_path: str, 

90 input_topology2_path: str, 

91 output_log_path: str, 

92 output_structure1_path: str, 

93 output_structure2_path: str, 

94 output_topology_path: str, 

95 output_atomtypes_path: str, 

96 input_scaffold1_path: Optional[str] = None, 

97 input_scaffold2_path: Optional[str] = None, 

98 input_pairs_path: Optional[str] = None, 

99 properties: Optional[dict] = None, 

100 **kwargs, 

101 ) -> None: 

102 properties = properties or {} 

103 

104 # Call parent class constructor 

105 super().__init__(properties) 

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

107 

108 # Input/Output files 

109 self.io_dict = { 

110 "in": { 

111 "input_structure1_path": input_structure1_path, 

112 "input_structure2_path": input_structure2_path, 

113 "input_topology1_path": input_topology1_path, 

114 "input_topology2_path": input_topology2_path, 

115 "input_scaffold1_path": input_scaffold1_path, 

116 "input_scaffold2_path": input_scaffold2_path, 

117 "input_pairs_path": input_pairs_path, 

118 }, 

119 "out": { 

120 "output_structure1_path": output_structure1_path, 

121 "output_structure2_path": output_structure2_path, 

122 "output_topology_path": output_topology_path, 

123 "output_atomtypes_path": output_atomtypes_path, 

124 "output_log_path": output_log_path, 

125 }, 

126 } 

127 

128 # Properties specific for BB 

129 # self.fit = properties.get('fit', False) 

130 # self.split = properties.get('split', False) 

131 # self.scDUMm = properties.get('scDUMm', 1.0) 

132 # self.scDUMa = properties.get('scDUMa', 1.0) 

133 # self.scDUMd = properties.get('scDUMd', 1.0) 

134 # self.deAng = properties.get('deAng', False) 

135 # self.distance = properties.get('distance', 0.05) 

136 

137 self.fit = properties.get("fit") 

138 self.split = properties.get("split") 

139 self.scDUMm = properties.get("scDUMm") 

140 self.scDUMa = properties.get("scDUMa") 

141 self.scDUMd = properties.get("scDUMd") 

142 self.deAng = properties.get("deAng") 

143 self.distance = properties.get("distance") 

144 

145 # Properties common in all PMX BB 

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

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

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

149 self.gmx_lib = str( 

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

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

152 ) 

153 ) 

154 if properties.get("container_path"): 

155 self.gmx_lib = str( 

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

157 "lib/python3.7/site-packages/pmx/data/mutff/" 

158 ) 

159 ) 

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

161 

162 # Check the properties 

163 self.check_properties(properties) 

164 self.check_arguments() 

165 

166 @launchlogger 

167 def launch(self) -> int: 

168 """Execute the :class:`Pmxmutate <pmx.pmxmutate.Pmxmutate>` pmx.pmxmutate.Pmxmutate object.""" 

169 

170 # Setup Biobb 

171 if self.check_restart(): 

172 return 0 

173 self.stage_files() 

174 

175 if self.container_path: 

176 working_dir = self.container_volume_path if self.container_volume_path else "/data" 

177 else: 

178 working_dir = self.stage_io_dict.get("unique_dir", "") 

179 

180 # Check if executable exists 

181 if not self.container_path: 

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

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

184 raise FileNotFoundError( 

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

186 % self.binary_path 

187 ) 

188 

189 self.cmd = [ 

190 "cd", 

191 working_dir, 

192 ";", 

193 self.binary_path, 

194 "ligandHybrid", 

195 "-i1", 

196 PurePath(self.stage_io_dict["in"]["input_structure1_path"]).name, 

197 "-i2", 

198 PurePath(self.stage_io_dict["in"]["input_structure2_path"]).name, 

199 "-itp1", 

200 PurePath(self.stage_io_dict["in"]["input_topology1_path"]).name, 

201 "-itp2", 

202 PurePath(self.stage_io_dict["in"]["input_topology2_path"]).name, 

203 "-oA", 

204 PurePath(self.stage_io_dict["out"]["output_structure1_path"]).name, 

205 "-oB", 

206 PurePath(self.stage_io_dict["out"]["output_structure2_path"]).name, 

207 "-oitp", 

208 PurePath(self.stage_io_dict["out"]["output_topology_path"]).name, 

209 "-offitp", 

210 PurePath(self.stage_io_dict["out"]["output_atomtypes_path"]).name, 

211 "-log", 

212 PurePath(self.stage_io_dict["out"]["output_log_path"]).name, 

213 ] 

214 

215 if self.stage_io_dict["in"].get("input_pairs_path"): 

216 self.cmd.append("-pairs") 

217 self.cmd.append(PurePath(self.stage_io_dict["in"]["input_pairs_path"]).name) 

218 

219 if self.stage_io_dict["in"].get("input_scaffold1_path"): 

220 self.cmd.append("-n1") 

221 self.cmd.append(PurePath(self.stage_io_dict["in"]["input_scaffold1_path"]).name) 

222 

223 if self.stage_io_dict["in"].get("input_scaffold2_path"): 

224 self.cmd.append("-n2") 

225 self.cmd.append(PurePath(self.stage_io_dict["in"]["input_scaffold2_path"]).name) 

226 

227 if self.fit: 

228 self.cmd.append("--fit") 

229 if self.split: 

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

231 if self.deAng: 

232 self.cmd.append("--deAng") 

233 if self.distance: 

234 self.cmd.append("--d") 

235 self.cmd.append(str(self.distance)) 

236 if self.scDUMm: 

237 self.cmd.append("--scDUMm") 

238 self.cmd.append(str(self.scDUMm)) 

239 if self.scDUMa: 

240 self.cmd.append("--scDUMa") 

241 self.cmd.append(str(self.scDUMa)) 

242 if self.scDUMd: 

243 self.cmd.append("--scDUMd") 

244 self.cmd.append(str(self.scDUMd)) 

245 

246 if self.gmx_lib: 

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

248 

249 # Run Biobb block 

250 self.run_biobb() 

251 

252 # Copy files to host 

253 self.copy_to_host() 

254 

255 self.remove_tmp_files() 

256 

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

258 return self.return_code 

259 

260 

261def pmxligand_hybrid( 

262 input_structure1_path: str, 

263 input_structure2_path: str, 

264 input_topology1_path: str, 

265 input_topology2_path: str, 

266 output_log_path: str, 

267 output_structure1_path: str, 

268 output_structure2_path: str, 

269 output_topology_path: str, 

270 output_atomtypes_path: str, 

271 input_scaffold1_path: Optional[str] = None, 

272 input_scaffold2_path: Optional[str] = None, 

273 input_pairs_path: Optional[str] = None, 

274 properties: Optional[dict] = None, 

275 **kwargs, 

276) -> int: 

277 """Create the :class:`Pmxligand_hybrid <pmx.pmxmutate.Pmxligand_hybrid>` class and 

278 execute the :meth:`launch() <pmx.pmxligand_hybrid.Pmxligand_hybrid.launch> method.""" 

279 return Pmxligand_hybrid(**dict(locals())).launch() 

280 

281 

282pmxligand_hybrid.__doc__ = Pmxligand_hybrid.__doc__ 

283main = Pmxligand_hybrid.get_main(pmxligand_hybrid, "Run PMX ligand hybrid module") 

284 

285if __name__ == "__main__": 

286 main()