Coverage for biobb_gromacs/gromacs/genion.py: 87%
79 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-28 06:50 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-28 06:50 +0000
1#!/usr/bin/env python3
3"""Module containing the Genion class and the command line interface."""
5import shutil
6from pathlib import Path, PurePath
7from typing import Optional, Union
9from biobb_common.generic.biobb_object import BiobbObject
10from biobb_common.tools import file_utils as fu
11from biobb_common.tools.file_utils import launchlogger
13from biobb_gromacs.gromacs.common import get_gromacs_version
16class Genion(BiobbObject):
17 """
18 | biobb_gromacs Genion
19 | Wrapper class for the `GROMACS genion <http://manual.gromacs.org/current/onlinehelp/gmx-genion.html>`_ module.
20 | The GROMACS genion module randomly replaces solvent molecules with monoatomic ions. The group of solvent molecules should be continuous and all molecules should have the same number of atoms.
22 Args:
23 input_tpr_path (str): Path to the input portable run input TPR file. File type: input. `Sample file <https://github.com/bioexcel/biobb_gromacs/raw/master/biobb_gromacs/test/data/gromacs/genion.tpr>`_. Accepted formats: tpr (edam:format_2333).
24 output_gro_path (str): Path to the input structure GRO file. File type: output. `Sample file <https://github.com/bioexcel/biobb_gromacs/raw/master/biobb_gromacs/test/reference/gromacs/ref_genion.gro>`_. Accepted formats: gro (edam:format_2033).
25 input_top_zip_path (str): Path the input TOP topology in zip format. File type: input. `Sample file <https://github.com/bioexcel/biobb_gromacs/raw/master/biobb_gromacs/test/data/gromacs/genion.zip>`_. Accepted formats: zip (edam:format_3987).
26 output_top_zip_path (str): Path the output topology TOP and ITP files zipball. File type: output. `Sample file <https://github.com/bioexcel/biobb_gromacs/raw/master/biobb_gromacs/test/reference/gromacs/ref_genion.zip>`_. Accepted formats: zip (edam:format_3987).
27 input_ndx_path (str) (Optional): Path to the input index NDX file. File type: input. Accepted formats: ndx (edam:format_2033).
28 properties (dict - Python dictionary object containing the tool parameters, not input/output files):
29 * **replaced_group** (*str*) - ("SOL") Group of molecules that will be replaced by the solvent.
30 * **neutral** (*bool*) - (False) Neutralize the charge of the system.
31 * **concentration** (*float*) - (0.0) [0~10|0.01] Concentration of the ions in (mol/liter).
32 * **seed** (*int*) - (1993) Seed for random number generator.
33 * **gmx_lib** (*str*) - (None) Path set GROMACS GMXLIB environment variable.
34 * **binary_path** (*str*) - ("gmx") Path to the GROMACS executable binary.
35 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
36 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
37 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
38 * **container_path** (*str*) - (None) Path to the binary executable of your container.
39 * **container_image** (*str*) - ("gromacs/gromacs:latest") Container Image identifier.
40 * **container_volume_path** (*str*) - ("/data") Path to an internal directory in the container.
41 * **container_working_dir** (*str*) - (None) Path to the internal CWD in the container.
42 * **container_user_id** (*str*) - (None) User number id to be mapped inside the container.
43 * **container_shell_path** (*str*) - ("/bin/bash") Path to the binary executable of the container shell.
45 Examples:
46 This is a use example of how to use the building block from Python::
48 from biobb_gromacs.gromacs.genion import genion
49 prop = { 'concentration': 0.05,
50 'replaced_group': 'SOL'}
51 genion(input_tpr_path='/path/to/myPortableBinaryRunInputFile.tpr',
52 output_gro_path='/path/to/newStructure.gro',
53 input_top_zip_path='/path/to/myTopology.zip',
54 output_top_zip_path='/path/to/newTopology.zip',
55 properties=prop)
57 Info:
58 * wrapped_software:
59 * name: GROMACS Genion
60 * version: 2025.2
61 * license: LGPL 2.1
62 * ontology:
63 * name: EDAM
64 * schema: http://edamontology.org/EDAM.owl
65 """
67 def __init__(
68 self,
69 input_tpr_path: Union[str, Path],
70 output_gro_path: Union[str, Path],
71 input_top_zip_path: Union[str, Path],
72 output_top_zip_path: Union[str, Path],
73 input_ndx_path: Optional[Union[str, Path]] = None,
74 properties: Optional[dict] = None,
75 **kwargs,
76 ) -> None:
77 properties = properties or {}
79 # Call parent class constructor
80 super().__init__(properties)
81 self.locals_var_dict = locals().copy()
83 # Input/Output files
84 self.io_dict = {
85 "in": {"input_tpr_path": input_tpr_path, "input_ndx_path": input_ndx_path},
86 "out": {
87 "output_gro_path": output_gro_path,
88 "output_top_zip_path": output_top_zip_path,
89 },
90 }
91 # Should not be copied inside container
92 self.input_top_zip_path = input_top_zip_path
94 # Properties specific for BB
95 self.output_top_path = properties.get(
96 "output_top_path", "gio.top"
97 ) # Not in documentation for clarity
98 self.replaced_group = properties.get("replaced_group", "SOL")
99 self.neutral = properties.get("neutral", False)
100 self.concentration = properties.get("concentration", 0.0)
101 self.seed = properties.get("seed", 1993)
103 # Properties common in all GROMACS BB
104 self.gmx_lib = properties.get("gmx_lib", None)
105 self.binary_path = properties.get("binary_path", "gmx")
106 self.gmx_nobackup = properties.get("gmx_nobackup", True)
107 self.gmx_nocopyright = properties.get("gmx_nocopyright", True)
108 if self.gmx_nobackup:
109 self.binary_path = f"{self.binary_path} -nobackup"
110 if self.gmx_nocopyright:
111 self.binary_path = f"{self.binary_path} -nocopyright"
112 if not self.container_path:
113 self.gmx_version = get_gromacs_version(str(self.binary_path))
115 # Check the properties
116 self.check_properties(properties)
117 self.check_arguments()
119 @launchlogger
120 def launch(self) -> int:
121 """Execute the :class:`Genion <gromacs.genion.Genion>` object."""
123 # Setup Biobb
124 if self.check_restart():
125 return 0
127 self.io_dict["in"]["stdin_file_path"] = fu.create_stdin_file(f"{self.replaced_group}")
128 self.stage_files()
130 # Unzip topology to topology_out
131 top_file = fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log)
132 top_file = str(Path(top_file).resolve()) # resolve before cd changes context
133 top_dir = str(Path(top_file).parent)
135 if self.container_path:
136 shutil.copytree(
137 top_dir,
138 Path(str(self.stage_io_dict.get("unique_dir", ""))).joinpath(
139 Path(top_dir).name
140 ),
141 )
142 top_file = str(Path(Path(top_dir).name).joinpath(Path(top_file).name))
144 if self.container_path:
145 working_dir = self.container_volume_path if self.container_volume_path else "/data"
146 else:
147 working_dir = self.stage_io_dict.get('unique_dir', '')
149 self.cmd = [
150 "cd", working_dir, ";",
151 str(self.binary_path),
152 "genion",
153 "-s",
154 PurePath(self.stage_io_dict["in"]["input_tpr_path"]).name,
155 "-o",
156 PurePath(self.stage_io_dict["out"]["output_gro_path"]).name,
157 "-p",
158 top_file,
159 ]
161 if (
162 self.stage_io_dict["in"].get("input_ndx_path") and Path(self.stage_io_dict["in"].get("input_ndx_path")).exists()
163 ):
164 self.cmd.append("-n")
165 self.cmd.append(PurePath(self.stage_io_dict["in"].get("input_ndx_path")).name)
167 if self.neutral:
168 self.cmd.append("-neutral")
170 if self.concentration:
171 self.cmd.append("-conc")
172 self.cmd.append(str(self.concentration))
173 fu.log(
174 "To reach up %g mol/litre concentration" % self.concentration,
175 self.out_log,
176 self.global_log,
177 )
179 if self.seed is not None:
180 self.cmd.append("-seed")
181 self.cmd.append(str(self.seed))
183 # Add stdin input file
184 self.cmd.append("<")
185 self.cmd.append(PurePath(self.stage_io_dict["in"]["stdin_file_path"]).name)
187 if self.gmx_lib:
188 self.env_vars_dict["GMXLIB"] = self.gmx_lib
190 # Run Biobb block
191 self.run_biobb()
193 # Copy files to host
194 self.copy_to_host()
196 if self.container_path:
197 top_file = str(
198 Path(str(self.stage_io_dict.get("unique_dir", ""))).joinpath(
199 Path(top_dir).name, Path(top_file).name
200 )
201 )
203 # zip topology
204 fu.log(
205 "Compressing topology to: %s"
206 % self.stage_io_dict["out"]["output_top_zip_path"],
207 self.out_log,
208 self.global_log,
209 )
210 fu.zip_top(
211 zip_file=self.io_dict["out"]["output_top_zip_path"],
212 top_file=top_file,
213 out_log=self.out_log,
214 remove_original_files=self.remove_tmp
215 )
217 # Remove temporal files
218 self.tmp_files.extend([top_dir, str(self.io_dict["in"].get("stdin_file_path"))])
219 self.remove_tmp_files()
221 self.check_arguments(output_files_created=True, raise_exception=True)
222 return self.return_code
225def genion(
226 input_tpr_path: Union[str, Path],
227 output_gro_path: Union[str, Path],
228 input_top_zip_path: Union[str, Path],
229 output_top_zip_path: Union[str, Path],
230 input_ndx_path: Optional[Union[str, Path]] = None,
231 properties: Optional[dict] = None,
232 **kwargs,
233) -> int:
234 """Create :class:`Genion <gromacs.genion.Genion>` class and
235 execute the :meth:`launch() <gromacs.genion.Genion.launch>` method."""
236 return Genion(**dict(locals())).launch()
239genion.__doc__ = Genion.__doc__
240main = Genion.get_main(genion, "Wrapper for the GROMACS genion module.")
243if __name__ == "__main__":
244 main()