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
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 06:59 +0000
1#!/usr/bin/env python3
3"""Module containing the PMX atom_mapping class and the command line interface."""
5import os
6import shutil
7import sys
8from pathlib import Path, PurePath
9from typing import Optional
11from biobb_common.generic.biobb_object import BiobbObject
12from biobb_common.tools.file_utils import launchlogger
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.
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.
57 Examples:
58 This is a use example of how to use the building block from Python::
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)
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
81 """
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 {}
102 # Call parent class constructor
103 super().__init__(properties)
104 self.locals_var_dict = locals().copy()
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 }
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)
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")
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")
168 # Check the properties
169 self.check_properties(properties)
170 self.check_arguments()
172 @launchlogger
173 def launch(self) -> int:
174 """Execute the :class:`Pmxmutate <pmx.pmxmutate.Pmxmutate>` pmx.pmxmutate.Pmxmutate object."""
176 # Setup Biobb
177 if self.check_restart():
178 return 0
179 self.stage_files()
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", "")
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 )
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 ]
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)
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)
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)
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)
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)
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)
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)
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))
266 if self.gmx_lib:
267 self.env_vars_dict["GMXLIB"] = self.gmx_lib
269 # Run Biobb block
270 self.run_biobb()
272 # Copy files to host
273 self.copy_to_host()
275 self.remove_tmp_files()
277 self.check_arguments(output_files_created=True, raise_exception=False)
278 return self.return_code
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()
302pmxatom_mapping.__doc__ = Pmxatom_mapping.__doc__
303main = Pmxatom_mapping.get_main(pmxatom_mapping, "Run PMX atom mapping module")
305if __name__ == "__main__":
306 main()