Coverage for biobb_gromacs / gromacs / genion.py: 88%

75 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-05 08:26 +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 

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_dir = str(Path(top_file).parent) 

133 

134 if self.container_path: 

135 shutil.copytree( 

136 top_dir, 

137 Path(str(self.stage_io_dict.get("unique_dir", ""))).joinpath( 

138 Path(top_dir).name 

139 ), 

140 ) 

141 top_file = str( 

142 Path(self.container_volume_path).joinpath( 

143 Path(top_dir).name, Path(top_file).name 

144 ) 

145 ) 

146 

147 self.cmd = [ 

148 str(self.binary_path), 

149 "genion", 

150 "-s", 

151 self.stage_io_dict["in"]["input_tpr_path"], 

152 "-o", 

153 self.stage_io_dict["out"]["output_gro_path"], 

154 "-p", 

155 top_file, 

156 ] 

157 

158 if ( 

159 self.stage_io_dict["in"].get("input_ndx_path") and Path(self.stage_io_dict["in"].get("input_ndx_path")).exists() 

160 ): 

161 self.cmd.append("-n") 

162 self.cmd.append(self.stage_io_dict["in"].get("input_ndx_path")) 

163 

164 if self.neutral: 

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

166 

167 if self.concentration: 

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

169 self.cmd.append(str(self.concentration)) 

170 fu.log( 

171 "To reach up %g mol/litre concentration" % self.concentration, 

172 self.out_log, 

173 self.global_log, 

174 ) 

175 

176 if self.seed is not None: 

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

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

179 

180 # Add stdin input file 

181 self.cmd.append("<") 

182 self.cmd.append(self.stage_io_dict["in"]["stdin_file_path"]) 

183 

184 if self.gmx_lib: 

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

186 

187 # Run Biobb block 

188 self.run_biobb() 

189 

190 # Copy files to host 

191 self.copy_to_host() 

192 

193 if self.container_path: 

194 top_file = str( 

195 Path(str(self.stage_io_dict.get("unique_dir", ""))).joinpath( 

196 Path(top_dir).name, Path(top_file).name 

197 ) 

198 ) 

199 

200 # zip topology 

201 fu.log( 

202 "Compressing topology to: %s" 

203 % self.stage_io_dict["out"]["output_top_zip_path"], 

204 self.out_log, 

205 self.global_log, 

206 ) 

207 fu.zip_top( 

208 zip_file=self.io_dict["out"]["output_top_zip_path"], 

209 top_file=top_file, 

210 out_log=self.out_log, 

211 remove_original_files=self.remove_tmp 

212 ) 

213 

214 # Remove temporal files 

215 self.tmp_files.extend([top_dir, str(self.io_dict["in"].get("stdin_file_path"))]) 

216 self.remove_tmp_files() 

217 

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

219 return self.return_code 

220 

221 

222def genion( 

223 input_tpr_path: Union[str, Path], 

224 output_gro_path: Union[str, Path], 

225 input_top_zip_path: Union[str, Path], 

226 output_top_zip_path: Union[str, Path], 

227 input_ndx_path: Optional[Union[str, Path]] = None, 

228 properties: Optional[dict] = None, 

229 **kwargs, 

230) -> int: 

231 """Create :class:`Genion <gromacs.genion.Genion>` class and 

232 execute the :meth:`launch() <gromacs.genion.Genion.launch>` method.""" 

233 return Genion(**dict(locals())).launch() 

234 

235 

236genion.__doc__ = Genion.__doc__ 

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

238 

239 

240if __name__ == "__main__": 

241 main()