Coverage for biobb_pmx/pmxbiobb/pmxatom_mapping.py: 71%

104 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 atom_mapping 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 Pmxatom_mapping(BiobbObject): 

16 """ 

17 | biobb_pmx Pmxatom_mapping 

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

19 | Perform atom mapping between 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 output_pairs1_path (str): Path to the output pairs for the ligand structure 1. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_pairs1.dat>`_. Accepted formats: dat (edam:format_1637), txt (edam:format_2330). 

25 output_pairs2_path (str): Path to the output pairs for the ligand structure 2. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_pairs2.dat>`_. Accepted formats: dat (edam:format_1637), txt (edam:format_2330). 

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

27 output_structure1_path (str) (Optional): Path to the superimposed structure for the ligand structure 1. File type: output. Accepted formats: pdb (edam:format_1476). 

28 output_structure2_path (str) (Optional): Path to the superimposed structure for the ligand structure 2. File type: output. Accepted formats: pdb (edam:format_1476). 

29 output_morph1_path (str) (Optional): Path to the morphable atoms for the ligand structure 1. File type: output. Accepted formats: pdb (edam:format_1476). 

30 output_morph2_path (str) (Optional): Path to the morphable atoms for the ligand structure 2. File type: output. Accepted formats: pdb (edam:format_1476). 

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

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

33 output_score_path (str) (Optional): Path to the morphing score. File type: output. Accepted formats: dat (edam:format_1637), txt (edam:format_2330). 

34 properties (dic): 

35 * **noalignment** (*bool*) - (False) Should the alignment method be disabled. 

36 * **nomcs** (*bool*) - (False) Should the MCS method be disabled. 

37 * **noH2H** (*bool*) - (True) Should non-polar hydrogens be discarded from morphing into any other hydrogen. 

38 * **H2Hpolar** (*bool*) - (False) Should polar hydrogens be morphed into polar hydrogens. 

39 * **H2Heavy** (*bool*) - (False) Should hydrogen be morphed into a heavy atom. 

40 * **RingsOnly** (*bool*) - (False) Should rings only be used in the MCS search and alignemnt. 

41 * **dMCS** (*bool*) - (False) Should the distance criterium be also applied in the MCS based search. 

42 * **swap** (*bool*) - (False) Try swapping the molecule order which would be a cross-check and require double execution time. 

43 * **nochirality** (*bool*) - (True) Perform chirality check for MCS mapping. 

44 * **distance** (*float*) - (0.05) Distance (nm) between atoms to consider them morphable for alignment approach. 

45 * **timeout** (*int*) - (10) Maximum time (s) for an MCS search. 

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

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

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

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

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

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

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

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

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

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

56 

57 Examples: 

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

59 

60 from biobb_pmx.pmxbiobb.pmxatom_mapping import pmxatom_mapping 

61 prop = { 

62 'no-alignment' : True, 

63 'distance': 0.05 

64 } 

65 pmxatom_mapping(input_structure1_path='/path/to/myStructure1.pdb', 

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

67 output_pairs1_path='/path/to/myPairs1.dat', 

68 output_pairs2_path='/path/to/myPairs2.dat', 

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

70 properties=prop) 

71 

72 Info: 

73 * wrapped_software: 

74 * name: PMX atom_mapping 

75 * version: >=1.0.1 

76 * license: GNU 

77 * ontology: 

78 * name: EDAM 

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

80 

81 """ 

82 

83 def __init__( 

84 self, 

85 input_structure1_path: str, 

86 input_structure2_path: str, 

87 output_pairs1_path: str, 

88 output_pairs2_path: str, 

89 output_log_path: str, 

90 output_structure1_path: Optional[str] = None, 

91 output_structure2_path: Optional[str] = None, 

92 output_morph1_path: Optional[str] = None, 

93 output_morph2_path: Optional[str] = None, 

94 output_scaffold1_path: Optional[str] = None, 

95 output_scaffold2_path: Optional[str] = None, 

96 output_score_path: Optional[str] = None, 

97 properties: Optional[dict] = None, 

98 **kwargs, 

99 ) -> None: 

100 properties = properties or {} 

101 

102 # Call parent class constructor 

103 super().__init__(properties) 

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

105 

106 # Input/Output files 

107 self.io_dict = { 

108 "in": { 

109 "input_structure1_path": input_structure1_path, 

110 "input_structure2_path": input_structure2_path, 

111 }, 

112 "out": { 

113 "output_pairs1_path": output_pairs1_path, 

114 "output_pairs2_path": output_pairs2_path, 

115 "output_log_path": output_log_path, 

116 "output_structure1_path": output_structure1_path, 

117 "output_structure2_path": output_structure2_path, 

118 "output_morph1_path": output_morph1_path, 

119 "output_morph2_path": output_morph2_path, 

120 "output_scaffold1_path": output_scaffold1_path, 

121 "output_scaffold2_path": output_scaffold2_path, 

122 "output_score_path": output_score_path, 

123 }, 

124 } 

125 

126 # Properties specific for BB 

127 # self.noalignment = properties.get('noalignment', False) 

128 # self.nomcs = properties.get('nomcs', False) 

129 # self.noH2H = properties.get('noH2H', True) 

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

131 # self.H2Heavy = properties.get('H2Heavy', False) 

132 # self.RingsOnly = properties.get('RingsOnly', False) 

133 # self.dMCS = properties.get('dMCS', False) 

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

135 # self.nochirality = properties.get('nochirality', True) 

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

137 # self.timeout = properties.get('timeout', 10) 

138 

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

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

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

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

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

144 self.RingsOnly = properties.get("RingsOnly") 

145 self.dMCS = properties.get("dMCS") 

146 self.swap = properties.get("swap") 

147 self.nochirality = properties.get("nochirality") 

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

149 self.timeout = properties.get("timeout") 

150 

151 # Properties common in all PMX BB 

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

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

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

155 self.gmx_lib = str( 

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

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

158 ) 

159 ) 

160 if properties.get("container_path"): 

161 self.gmx_lib = str( 

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

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

164 ) 

165 ) 

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

167 

168 # Check the properties 

169 self.check_properties(properties) 

170 self.check_arguments() 

171 

172 @launchlogger 

173 def launch(self) -> int: 

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

175 

176 # Setup Biobb 

177 if self.check_restart(): 

178 return 0 

179 self.stage_files() 

180 

181 if self.container_path: 

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

183 else: 

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

185 

186 # Check if executable exists 

187 if not self.container_path: 

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

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

190 raise FileNotFoundError( 

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

192 % self.binary_path 

193 ) 

194 

195 self.cmd = [ 

196 "cd", 

197 working_dir, 

198 ";", 

199 self.binary_path, 

200 "atomMapping", 

201 "-i1", 

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

203 "-i2", 

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

205 "-o1", 

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

207 "-o2", 

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

209 "-log", 

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

211 ] 

212 

213 if self.stage_io_dict["out"].get("output_structure1_path"): 

214 self.cmd.append("-opdb1") 

215 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_structure1_path"]).name) 

216 

217 if self.stage_io_dict["out"].get("output_structure2_path"): 

218 self.cmd.append("-opdb2") 

219 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_structure2_path"]).name) 

220 

221 if self.stage_io_dict["out"].get("output_morph1_path"): 

222 self.cmd.append("-opdbm1") 

223 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_morph1_path"]).name) 

224 

225 if self.stage_io_dict["out"].get("output_morph2_path"): 

226 self.cmd.append("-opdbm2") 

227 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_morph2_path"]).name) 

228 

229 if self.stage_io_dict["out"].get("output_scaffold1_path"): 

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

231 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_scaffold1_path"]).name) 

232 

233 if self.stage_io_dict["out"].get("output_scaffold2_path"): 

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

235 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_scaffold2_path"]).name) 

236 

237 if self.stage_io_dict["out"].get("output_score_path"): 

238 self.cmd.append("-score") 

239 self.cmd.append(PurePath(self.stage_io_dict["out"]["output_score_path"]).name) 

240 

241 if self.noalignment: 

242 self.cmd.append("--no-alignment") 

243 if self.nomcs: 

244 self.cmd.append("--no-mcs") 

245 if self.noH2H: 

246 self.cmd.append("--no-H2H") 

247 if self.H2Hpolar: 

248 self.cmd.append("--H2Hpolar") 

249 if self.H2Heavy: 

250 self.cmd.append("--H2Heavy") 

251 if self.RingsOnly: 

252 self.cmd.append("--RingsOnly") 

253 if self.dMCS: 

254 self.cmd.append("--dMCS") 

255 if self.swap: 

256 self.cmd.append("--swap") 

257 if self.nochirality: 

258 self.cmd.append("--no-chirality") 

259 if self.distance: 

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

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

262 if self.timeout: 

263 self.cmd.append("--timeout") 

264 self.cmd.append(str(self.timeout)) 

265 

266 if self.gmx_lib: 

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

268 

269 # Run Biobb block 

270 self.run_biobb() 

271 

272 # Copy files to host 

273 self.copy_to_host() 

274 

275 self.remove_tmp_files() 

276 

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

278 return self.return_code 

279 

280 

281def pmxatom_mapping( 

282 input_structure1_path: str, 

283 input_structure2_path: str, 

284 output_pairs1_path: str, 

285 output_pairs2_path: str, 

286 output_log_path: str, 

287 output_structure1_path: Optional[str] = None, 

288 output_structure2_path: Optional[str] = None, 

289 output_morph1_path: Optional[str] = None, 

290 output_morph2_path: Optional[str] = None, 

291 output_scaffold1_path: Optional[str] = None, 

292 output_scaffold2_path: Optional[str] = None, 

293 output_score_path: Optional[str] = None, 

294 properties: Optional[dict] = None, 

295 **kwargs, 

296) -> int: 

297 """Create the :class:`Pmxatom_mapping <pmx.pmxmutate.Pmxatom_mapping>` class and 

298 execute the :meth:`launch() <pmx.pmxatom_mapping.Pmxatom_mapping.launch> method.""" 

299 return Pmxatom_mapping(**dict(locals())).launch() 

300 

301 

302pmxatom_mapping.__doc__ = Pmxatom_mapping.__doc__ 

303main = Pmxatom_mapping.get_main(pmxatom_mapping, "Run PMX atom mapping module") 

304 

305if __name__ == "__main__": 

306 main()