Coverage for biobb_pmx/pmxbiobb/pmxgentop.py: 91%
78 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 gentop class and the command line interface."""
5import os
6import shutil
7import sys
8from pathlib import Path
9from typing import Optional
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
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.
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/blob/master/biobb_pmx/test/data/pmx/mut_gmx.top.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.
41 Examples:
42 This is a use example of how to use the building block from Python::
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)
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
62 """
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 {}
73 # Call parent class constructor
74 super().__init__(properties)
75 self.locals_var_dict = locals().copy()
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
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)
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")
104 # Check the properties
105 self.check_properties(properties)
106 self.check_arguments()
108 @launchlogger
109 def launch(self) -> int:
110 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` pmx.pmxgentop.Pmxgentop object."""
112 # Setup Biobb
113 if self.check_restart():
114 return 0
115 self.stage_files()
117 if self.container_path:
118 working_dir = self.container_volume_path if self.container_volume_path else "/data"
119 else:
120 working_dir = self.stage_io_dict.get("unique_dir", "")
122 # Check if executable exists
123 if not self.container_path:
124 if not Path(self.binary_path).is_file():
125 if not shutil.which(self.binary_path):
126 raise FileNotFoundError(
127 "Executable %s not found. Check if it is installed in your system and correctly defined in the properties"
128 % self.binary_path
129 )
131 # Unzip topology to topology_out
132 top_file = str(
133 Path(
134 fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log)
135 ).resolve()
136 )
137 top_dir = str(Path(top_file).parent)
139 # Copy extra files to sandbox: topology folder
140 top_dir_in_sandbox = Path(self.stage_io_dict.get("unique_dir", "")).joinpath(
141 Path(top_dir).name
142 )
143 shutil.copytree(top_dir, str(top_dir_in_sandbox))
144 top_file = str(Path(Path(top_dir).name).joinpath(Path(top_file).name))
146 output_file_name = fu.create_name(
147 prefix=self.prefix, step=self.step, name=str(Path(top_file).name)
148 )
149 unique_dir_output_file = str(
150 Path(self.stage_io_dict.get("unique_dir", "")).joinpath(output_file_name)
151 )
152 fu.log(f"unique_dir_output_file: {unique_dir_output_file}", self.out_log)
154 self.cmd = [
155 "cd",
156 working_dir,
157 ";",
158 self.binary_path,
159 "gentop",
160 "-o",
161 str(Path(output_file_name)),
162 "-ff",
163 self.force_field,
164 "-p",
165 top_file,
166 ]
168 if self.split:
169 self.cmd.append("--split")
170 if self.scale_mass:
171 self.cmd.append("--scale_mass")
173 if self.gmx_lib:
174 self.env_vars_dict["GMXLIB"] = self.gmx_lib
176 # Run Biobb block
177 self.run_biobb()
179 # Copy files to host
180 self.copy_to_host()
182 # Remove paths from top file
183 with open(Path(unique_dir_output_file)) as top_fh:
184 top_lines = top_fh.readlines()
185 with open(Path(unique_dir_output_file), "w") as top_fh:
186 for line in top_lines:
187 top_fh.write(
188 line.replace(str(Path(unique_dir_output_file).parent) + "/", "")
189 )
190 # Copy the not modified itp files
191 for orig_itp_file in Path(top_dir).iterdir():
192 fu.log(
193 f"Check if {str(Path(unique_dir_output_file).parent.joinpath(Path(orig_itp_file).name))} exists",
194 self.out_log,
195 self.global_log,
196 )
197 if (
198 not Path(unique_dir_output_file)
199 .parent.joinpath(Path(orig_itp_file).name)
200 .exists()
201 ):
202 shutil.copy(orig_itp_file, Path(unique_dir_output_file).parent)
203 fu.log(
204 f"Copying {str(orig_itp_file)} to: {str(Path(unique_dir_output_file).parent)}",
205 self.out_log,
206 self.global_log,
207 )
209 # zip topology
210 fu.log(
211 "Compressing topology to: %s" % self.io_dict["out"]["output_top_zip_path"],
212 self.out_log,
213 self.global_log,
214 )
215 fu.zip_top(
216 zip_file=self.io_dict["out"]["output_top_zip_path"],
217 top_file=str(Path(unique_dir_output_file)),
218 out_log=self.out_log,
219 remove_original_files=self.remove_tmp
220 )
222 self.tmp_files.extend([top_dir])
223 self.remove_tmp_files()
225 self.check_arguments(output_files_created=True, raise_exception=False)
226 return self.return_code
229def pmxgentop(
230 input_top_zip_path: str,
231 output_top_zip_path: str,
232 properties: Optional[dict] = None,
233 **kwargs,
234) -> int:
235 """Create the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` class and
236 execute the :meth:`launch() <pmx.pmxgentop.Pmxgentop.launch> method."""
237 return Pmxgentop(**dict(locals())).launch()
240pmxgentop.__doc__ = Pmxgentop.__doc__
241main = Pmxgentop.get_main(pmxgentop, "Wrapper class for the PMX gentop module")
243if __name__ == "__main__":
244 main()