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

101 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-19 14:26 +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 

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 

35 properties (dic): 

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

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

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

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

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

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

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

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

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

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

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

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 # Check if executable exists 

182 if not self.container_path: 

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

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

185 raise FileNotFoundError( 

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

187 % self.binary_path 

188 ) 

189 

190 self.cmd = [ 

191 self.binary_path, 

192 "atomMapping", 

193 "-i1", 

194 self.stage_io_dict["in"]["input_structure1_path"], 

195 "-i2", 

196 self.stage_io_dict["in"]["input_structure2_path"], 

197 "-o1", 

198 self.stage_io_dict["out"]["output_pairs1_path"], 

199 "-o2", 

200 self.stage_io_dict["out"]["output_pairs2_path"], 

201 "-log", 

202 self.stage_io_dict["out"]["output_log_path"], 

203 ] 

204 

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

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

207 self.cmd.append(self.stage_io_dict["out"]["output_structure1_path"]) 

208 

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

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

211 self.cmd.append(self.stage_io_dict["out"]["output_structure2_path"]) 

212 

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

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

215 self.cmd.append(self.stage_io_dict["out"]["output_morph1_path"]) 

216 

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

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

219 self.cmd.append(self.stage_io_dict["out"]["output_morph2_path"]) 

220 

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

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

223 self.cmd.append(self.stage_io_dict["out"]["output_scaffold1_path"]) 

224 

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

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

227 self.cmd.append(self.stage_io_dict["out"]["output_scaffold2_path"]) 

228 

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

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

231 self.cmd.append(self.stage_io_dict["out"]["output_score_path"]) 

232 

233 if self.noalignment: 

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

235 if self.nomcs: 

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

237 if self.noH2H: 

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

239 if self.H2Hpolar: 

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

241 if self.H2Heavy: 

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

243 if self.RingsOnly: 

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

245 if self.dMCS: 

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

247 if self.swap: 

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

249 if self.nochirality: 

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

251 if self.distance: 

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

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

254 if self.timeout: 

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

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

257 

258 if self.gmx_lib: 

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

260 

261 # Run Biobb block 

262 self.run_biobb() 

263 

264 # Copy files to host 

265 self.copy_to_host() 

266 

267 self.remove_tmp_files() 

268 

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

270 return self.return_code 

271 

272 

273def pmxatom_mapping( 

274 input_structure1_path: str, 

275 input_structure2_path: str, 

276 output_pairs1_path: str, 

277 output_pairs2_path: str, 

278 output_log_path: str, 

279 output_structure1_path: Optional[str] = None, 

280 output_structure2_path: Optional[str] = None, 

281 output_morph1_path: Optional[str] = None, 

282 output_morph2_path: Optional[str] = None, 

283 output_scaffold1_path: Optional[str] = None, 

284 output_scaffold2_path: Optional[str] = None, 

285 output_score_path: Optional[str] = None, 

286 properties: Optional[dict] = None, 

287 **kwargs, 

288) -> int: 

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

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

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

292 

293 

294pmxatom_mapping.__doc__ = Pmxatom_mapping.__doc__ 

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

296 

297if __name__ == "__main__": 

298 main()