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

1#!/usr/bin/env python3 

2 

3"""Module containing the Genion class and the command line interface.""" 

4 

5import shutil 

6from pathlib import Path, PurePath 

7from typing import Optional, Union 

8 

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 

12 

13from biobb_gromacs.gromacs.common import get_gromacs_version 

14 

15 

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. 

21 

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. 

44 

45 Examples: 

46 This is a use example of how to use the building block from Python:: 

47 

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) 

56 

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 """ 

66 

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 {} 

78 

79 # Call parent class constructor 

80 super().__init__(properties) 

81 self.locals_var_dict = locals().copy() 

82 

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 

93 

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) 

102 

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)) 

114 

115 # Check the properties 

116 self.check_properties(properties) 

117 self.check_arguments() 

118 

119 @launchlogger 

120 def launch(self) -> int: 

121 """Execute the :class:`Genion <gromacs.genion.Genion>` object.""" 

122 

123 # Setup Biobb 

124 if self.check_restart(): 

125 return 0 

126 

127 self.io_dict["in"]["stdin_file_path"] = fu.create_stdin_file(f"{self.replaced_group}") 

128 self.stage_files() 

129 

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) 

134 

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)) 

143 

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', '') 

148 

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 ] 

160 

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) 

166 

167 if self.neutral: 

168 self.cmd.append("-neutral") 

169 

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 ) 

178 

179 if self.seed is not None: 

180 self.cmd.append("-seed") 

181 self.cmd.append(str(self.seed)) 

182 

183 # Add stdin input file 

184 self.cmd.append("<") 

185 self.cmd.append(PurePath(self.stage_io_dict["in"]["stdin_file_path"]).name) 

186 

187 if self.gmx_lib: 

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

189 

190 # Run Biobb block 

191 self.run_biobb() 

192 

193 # Copy files to host 

194 self.copy_to_host() 

195 

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 ) 

202 

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 ) 

216 

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() 

220 

221 self.check_arguments(output_files_created=True, raise_exception=True) 

222 return self.return_code 

223 

224 

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() 

237 

238 

239genion.__doc__ = Genion.__doc__ 

240main = Genion.get_main(genion, "Wrapper for the GROMACS genion module.") 

241 

242 

243if __name__ == "__main__": 

244 main()