Coverage for biobb_pmx/pmxbiobb/pmxgentop.py: 73%
97 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 10:10 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 10:10 +0000
1#!/usr/bin/env python3
3"""Module containing the PMX gentop class and the command line interface."""
5import argparse
6import os
7import shutil
8import sys
9from pathlib import Path
10from typing import Optional
12from biobb_common.configuration import settings
13from biobb_common.generic.biobb_object import BiobbObject
14from biobb_common.tools import file_utils as fu
15from biobb_common.tools.file_utils import launchlogger
18class Pmxgentop(BiobbObject):
19 """
20 | biobb_pmx Pmxgentop
21 | Wrapper class for the `PMX gentop <https://github.com/deGrootLab/pmx>`_ module.
22 | Generate a topology file for a morphing simulation.
24 Args:
25 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/raw/master/biobb_pmx/test/data/pmx/topology.zip>`_. Accepted formats: zip (edam:format_3987).
26 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).
27 properties (dic):
28 * **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.
29 * **split** (*bool*) - (False) Write separate topologies for the vdW and charge transformations.
30 * **scale_mass** (*bool*) - (False) Scale the masses of morphing atoms so that dummies have a mass of 1.
31 * **gmx_lib** (*str*) - ("$CONDA_PREFIX/lib/python3.7/site-packages/pmx/data/mutff/") Path to the GMXLIB folder in your computer.
32 * **binary_path** (*str*) - ("pmx") Path to the PMX command line interface.
33 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
34 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
35 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
36 * **container_path** (*str*) - (None) Path to the binary executable of your container.
37 * **container_image** (*str*) - ("gromacs/gromacs:latest") Container Image identifier.
38 * **container_volume_path** (*str*) - ("/inout") Path to an internal directory in the container.
39 * **container_working_dir** (*str*) - (None) Path to the internal CWD in the container.
40 * **container_user_id** (*str*) - (None) User number id to be mapped inside the container.
41 * **container_shell_path** (*str*) - ("/bin/bash") Path to the binary executable of the container shell.
43 Examples:
44 This is a use example of how to use the building block from Python::
46 from biobb_pmx.pmxbiobb.pmxgentop import pmxgentop
47 prop = {
48 'gmx_lib': '/path/to/myGMXLIB/',
49 'force_field': 'amber99sb-star-ildn-mut'
50 }
51 pmxgentop(input_top_zip_path='/path/to/myTopology.zip',
52 output_top_zip_path='/path/to/newTopology.zip',
53 properties=prop)
55 Info:
56 * wrapped_software:
57 * name: PMX gentop
58 * version: >=1.0.1
59 * license: GNU
60 * ontology:
61 * name: EDAM
62 * schema: http://edamontology.org/EDAM.owl
64 """
66 def __init__(
67 self,
68 input_top_zip_path: str,
69 output_top_zip_path: str,
70 properties: Optional[dict] = None,
71 **kwargs,
72 ) -> None:
73 properties = properties or {}
75 # Call parent class constructor
76 super().__init__(properties)
77 self.locals_var_dict = locals().copy()
79 # Input/Output files
80 self.io_dict = {"in": {}, "out": {"output_top_zip_path": output_top_zip_path}}
81 # Should not be copied inside container
82 self.input_top_zip_path = input_top_zip_path
84 # Properties specific for BB
85 self.force_field = properties.get("force_field", "amber99sb-star-ildn-mut")
86 self.split = properties.get("split", False)
87 self.scale_mass = properties.get("scale_mass", False)
89 # Properties common in all PMX BB
90 self.gmx_lib = properties.get("gmx_lib", None)
91 if not self.gmx_lib and os.environ.get("CONDA_PREFIX", ""):
92 python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
93 self.gmx_lib = str(
94 Path(os.environ.get("CONDA_PREFIX", "")).joinpath(
95 f"lib/python{python_version}/site-packages/pmx/data/mutff/"
96 )
97 )
98 if properties.get("container_path"):
99 self.gmx_lib = str(
100 Path("/usr/local/").joinpath(
101 "lib/python3.8/site-packages/pmx/data/mutff/"
102 )
103 )
104 self.binary_path = properties.get("binary_path", "pmx")
106 # Check the properties
107 self.check_properties(properties)
108 self.check_arguments()
110 @launchlogger
111 def launch(self) -> int:
112 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` pmx.pmxgentop.Pmxgentop object."""
114 # Setup Biobb
115 if self.check_restart():
116 return 0
117 self.stage_files()
119 # Check if executable exists
120 if not self.container_path:
121 if not Path(self.binary_path).is_file():
122 if not shutil.which(self.binary_path):
123 raise FileNotFoundError(
124 "Executable %s not found. Check if it is installed in your system and correctly defined in the properties"
125 % self.binary_path
126 )
128 # Unzip topology to topology_out
129 top_file = fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log)
130 top_dir = str(Path(top_file).parent)
132 # Copy extra files to container: topology folder
133 if self.container_path:
134 fu.log("Container execution enabled", self.out_log)
135 fu.log(f"Unique dir: {self.stage_io_dict['unique_dir']}", self.out_log)
136 fu.log(
137 f"{self.stage_io_dict['unique_dir']} files: {os.listdir(self.stage_io_dict['unique_dir'])}",
138 self.out_log,
139 )
140 fu.log(
141 f"Copy all files of the unzipped original topology to unique dir: {self.out_log}"
142 )
143 shutil.copytree(
144 top_dir,
145 str(
146 Path(self.stage_io_dict.get("unique_dir", "")).joinpath(
147 Path(top_dir).name
148 )
149 ),
150 )
151 top_file = str(
152 Path(self.container_volume_path).joinpath(
153 Path(top_dir).name, Path(top_file).name
154 )
155 )
157 output_file_name = fu.create_name(
158 prefix=self.prefix, step=self.step, name=str(Path(top_file).name)
159 )
160 u_dir = fu.create_unique_dir()
161 unique_dir_output_file = str(
162 Path(u_dir).joinpath(output_file_name)
163 )
164 fu.log(f"unique_dir_output_file: {unique_dir_output_file}", self.out_log)
166 if self.container_path:
167 fu.log("Change references for container:", self.out_log)
168 unique_dir_output_file = str(
169 Path(self.container_volume_path).joinpath(Path(output_file_name))
170 )
171 fu.log(
172 f" unique_dir_output_file: {unique_dir_output_file}", self.out_log
173 )
175 self.cmd = [
176 self.binary_path,
177 "gentop",
178 "-o",
179 str(Path(unique_dir_output_file)),
180 "-ff",
181 self.force_field,
182 "-p",
183 top_file,
184 ]
186 if self.split:
187 self.cmd.append("--split")
188 if self.scale_mass:
189 self.cmd.append("--scale_mass")
191 if self.gmx_lib:
192 self.env_vars_dict["GMXLIB"] = self.gmx_lib
194 # Run Biobb block
195 self.run_biobb()
197 # Copy files to host
198 self.copy_to_host()
200 if self.container_path:
201 unique_dir_output_file = str(
202 Path(self.stage_io_dict.get("unique_dir", "")).joinpath(
203 Path(unique_dir_output_file).name
204 )
205 )
207 # Remove paths from top file
208 with open(Path(unique_dir_output_file)) as top_fh:
209 top_lines = top_fh.readlines()
210 with open(Path(unique_dir_output_file), "w") as top_fh:
211 for line in top_lines:
212 top_fh.write(
213 line.replace(str(Path(unique_dir_output_file).parent) + "/", "")
214 )
215 # Copy the not modified itp files
216 for orig_itp_file in Path(top_dir).iterdir():
217 fu.log(
218 f"Check if {str(Path(unique_dir_output_file).parent.joinpath(Path(orig_itp_file).name))} exists",
219 self.out_log,
220 self.global_log,
221 )
222 if (
223 not Path(unique_dir_output_file)
224 .parent.joinpath(Path(orig_itp_file).name)
225 .exists()
226 ):
227 shutil.copy(orig_itp_file, Path(unique_dir_output_file).parent)
228 fu.log(
229 f"Copying {str(orig_itp_file)} to: {str(Path(unique_dir_output_file).parent)}",
230 self.out_log,
231 self.global_log,
232 )
234 # zip topology
235 fu.log(
236 "Compressing topology to: %s" % self.io_dict["out"]["output_top_zip_path"],
237 self.out_log,
238 self.global_log,
239 )
240 fu.zip_top(
241 zip_file=self.io_dict["out"]["output_top_zip_path"],
242 top_file=str(Path(unique_dir_output_file)),
243 out_log=self.out_log,
244 )
246 self.tmp_files.extend([
247 # self.stage_io_dict.get("unique_dir", ""),
248 top_dir,
249 u_dir
250 ])
251 self.remove_tmp_files()
253 self.check_arguments(output_files_created=True, raise_exception=False)
254 return self.return_code
257def pmxgentop(
258 input_top_zip_path: str,
259 output_top_zip_path: str,
260 properties: Optional[dict] = None,
261 **kwargs,
262) -> int:
263 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` class and
264 execute the :meth:`launch() <pmx.pmxgentop.Pmxgentop.launch> method."""
266 return Pmxgentop(
267 input_top_zip_path=input_top_zip_path,
268 output_top_zip_path=output_top_zip_path,
269 properties=properties,
270 **kwargs,
271 ).launch()
273 pmxgentop.__doc__ = Pmxgentop.__doc__
276def main():
277 """Command line execution of this building block. Please check the command line documentation."""
278 parser = argparse.ArgumentParser(
279 description="Wrapper class for the PMX gentop module",
280 formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999),
281 )
282 parser.add_argument(
283 "-c",
284 "--config",
285 required=False,
286 help="This file can be a YAML file, JSON file or JSON string",
287 )
289 # Specific args of each building block
290 required_args = parser.add_argument_group("required arguments")
291 required_args.add_argument(
292 "--input_top_zip_path",
293 required=True,
294 help="Path to the input topology zip file",
295 )
296 required_args.add_argument(
297 "--output_top_zip_path",
298 required=True,
299 help="Path to the output topology zip file",
300 )
302 args = parser.parse_args()
303 config = args.config if args.config else None
304 properties = settings.ConfReader(config=config).get_prop_dic()
306 # Specific call of each building block
307 pmxgentop(
308 input_top_zip_path=args.input_top_zip_path,
309 output_top_zip_path=args.output_top_zip_path,
310 properties=properties,
311 )
314if __name__ == "__main__":
315 main()