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

89 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-10-21 09:54 +0000

1#!/usr/bin/env python3 

2 

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

4 

5import argparse 

6import shutil 

7from pathlib import Path 

8from typing import Optional, Union 

9 

10from biobb_common.configuration import settings 

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 

14 

15from biobb_gromacs.gromacs.common import get_gromacs_version 

16 

17 

18class Genion(BiobbObject): 

19 """ 

20 | biobb_gromacs Genion 

21 | Wrapper class for the `GROMACS genion <http://manual.gromacs.org/current/onlinehelp/gmx-genion.html>`_ module. 

22 | 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. 

23 

24 Args: 

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

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

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

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

29 input_ndx_path (str) (Optional): Path to the input index NDX file. File type: input. Accepted formats: ndx (edam:format_2033). 

30 properties (dict - Python dictionary object containing the tool parameters, not input/output files): 

31 * **replaced_group** (*str*) - ("SOL") Group of molecules that will be replaced by the solvent. 

32 * **neutral** (*bool*) - (False) Neutralize the charge of the system. 

33 * **concentration** (*float*) - (0.0) [0~10|0.01] Concentration of the ions in (mol/liter). 

34 * **seed** (*int*) - (1993) Seed for random number generator. 

35 * **gmx_lib** (*str*) - (None) Path set GROMACS GMXLIB environment variable. 

36 * **binary_path** (*str*) - ("gmx") Path to the GROMACS executable binary. 

37 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files. 

38 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist. 

39 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory. 

40 * **container_path** (*str*) - (None) Path to the binary executable of your container. 

41 * **container_image** (*str*) - ("gromacs/gromacs:latest") Container Image identifier. 

42 * **container_volume_path** (*str*) - ("/data") Path to an internal directory in the container. 

43 * **container_working_dir** (*str*) - (None) Path to the internal CWD in the container. 

44 * **container_user_id** (*str*) - (None) User number id to be mapped inside the container. 

45 * **container_shell_path** (*str*) - ("/bin/bash") Path to the binary executable of the container shell. 

46 

47 Examples: 

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

49 

50 from biobb_gromacs.gromacs.genion import genion 

51 prop = { 'concentration': 0.05, 

52 'replaced_group': 'SOL'} 

53 genion(input_tpr_path='/path/to/myPortableBinaryRunInputFile.tpr', 

54 output_gro_path='/path/to/newStructure.gro', 

55 input_top_zip_path='/path/to/myTopology.zip', 

56 output_top_zip_path='/path/to/newTopology.zip', 

57 properties=prop) 

58 

59 Info: 

60 * wrapped_software: 

61 * name: GROMACS Genion 

62 * version: 2025.2 

63 * license: LGPL 2.1 

64 * ontology: 

65 * name: EDAM 

66 * schema: http://edamontology.org/EDAM.owl 

67 """ 

68 

69 def __init__( 

70 self, 

71 input_tpr_path: Union[str, Path], 

72 output_gro_path: Union[str, Path], 

73 input_top_zip_path: Union[str, Path], 

74 output_top_zip_path: Union[str, Path], 

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

76 properties: Optional[dict] = None, 

77 **kwargs, 

78 ) -> None: 

79 properties = properties or {} 

80 

81 # Call parent class constructor 

82 super().__init__(properties) 

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

84 

85 # Input/Output files 

86 self.io_dict = { 

87 "in": {"input_tpr_path": input_tpr_path, "input_ndx_path": input_ndx_path}, 

88 "out": { 

89 "output_gro_path": output_gro_path, 

90 "output_top_zip_path": output_top_zip_path, 

91 }, 

92 } 

93 # Should not be copied inside container 

94 self.input_top_zip_path = input_top_zip_path 

95 

96 # Properties specific for BB 

97 self.output_top_path = properties.get( 

98 "output_top_path", "gio.top" 

99 ) # Not in documentation for clarity 

100 self.replaced_group = properties.get("replaced_group", "SOL") 

101 self.neutral = properties.get("neutral", False) 

102 self.concentration = properties.get("concentration", 0.0) 

103 self.seed = properties.get("seed", 1993) 

104 

105 # Properties common in all GROMACS BB 

106 self.gmx_lib = properties.get("gmx_lib", None) 

107 self.binary_path = properties.get("binary_path", "gmx") 

108 self.gmx_nobackup = properties.get("gmx_nobackup", True) 

109 self.gmx_nocopyright = properties.get("gmx_nocopyright", True) 

110 if self.gmx_nobackup: 

111 self.binary_path = f"{self.binary_path} -nobackup" 

112 if self.gmx_nocopyright: 

113 self.binary_path = f"{self.binary_path} -nocopyright" 

114 if not self.container_path: 

115 self.gmx_version = get_gromacs_version(str(self.binary_path)) 

116 

117 # Check the properties 

118 self.check_properties(properties) 

119 self.check_arguments() 

120 

121 @launchlogger 

122 def launch(self) -> int: 

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

124 

125 # Setup Biobb 

126 if self.check_restart(): 

127 return 0 

128 

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

130 self.stage_files() 

131 

132 # Unzip topology to topology_out 

133 top_file = fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log) 

134 top_dir = str(Path(top_file).parent) 

135 

136 if self.container_path: 

137 shutil.copytree( 

138 top_dir, 

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

140 Path(top_dir).name 

141 ), 

142 ) 

143 top_file = str( 

144 Path(self.container_volume_path).joinpath( 

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

146 ) 

147 ) 

148 

149 self.cmd = [ 

150 str(self.binary_path), 

151 "genion", 

152 "-s", 

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

154 "-o", 

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

156 "-p", 

157 top_file, 

158 ] 

159 

160 if ( 

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

162 ): 

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

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

165 

166 if self.neutral: 

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

168 

169 if self.concentration: 

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

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

172 fu.log( 

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

174 self.out_log, 

175 self.global_log, 

176 ) 

177 

178 if self.seed is not None: 

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

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

181 

182 # Add stdin input file 

183 self.cmd.append("<") 

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

185 

186 if self.gmx_lib: 

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

188 

189 # Run Biobb block 

190 self.run_biobb() 

191 

192 # Copy files to host 

193 self.copy_to_host() 

194 

195 if self.container_path: 

196 top_file = str( 

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

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

199 ) 

200 ) 

201 

202 # zip topology 

203 fu.log( 

204 "Compressing topology to: %s" 

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

206 self.out_log, 

207 self.global_log, 

208 ) 

209 fu.zip_top( 

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

211 top_file=top_file, 

212 out_log=self.out_log, 

213 remove_original_files=self.remove_tmp 

214 ) 

215 

216 # Remove temporal files 

217 self.tmp_files.extend( 

218 [ 

219 top_dir, 

220 str(self.io_dict["in"].get("stdin_file_path")), 

221 ] 

222 ) 

223 self.remove_tmp_files() 

224 

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

226 return self.return_code 

227 

228 

229def genion( 

230 input_tpr_path: Union[str, Path], 

231 output_gro_path: Union[str, Path], 

232 input_top_zip_path: Union[str, Path], 

233 output_top_zip_path: Union[str, Path], 

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

235 properties: Optional[dict] = None, 

236 **kwargs, 

237) -> int: 

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

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

240 return Genion( 

241 input_tpr_path=input_tpr_path, 

242 output_gro_path=output_gro_path, 

243 input_top_zip_path=input_top_zip_path, 

244 output_top_zip_path=output_top_zip_path, 

245 input_ndx_path=input_ndx_path, 

246 properties=properties, 

247 **kwargs, 

248 ).launch() 

249 

250 

251genion.__doc__ = Genion.__doc__ 

252 

253 

254def main(): 

255 """Command line execution of this building block. Please check the command line documentation.""" 

256 parser = argparse.ArgumentParser( 

257 description="Wrapper for the GROMACS genion module.", 

258 formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999), 

259 ) 

260 parser.add_argument( 

261 "-c", 

262 "--config", 

263 required=False, 

264 help="This file can be a YAML file, JSON file or JSON string", 

265 ) 

266 

267 # Specific args of each building block 

268 required_args = parser.add_argument_group("required arguments") 

269 required_args.add_argument("--input_tpr_path", required=True) 

270 required_args.add_argument("--output_gro_path", required=True) 

271 required_args.add_argument("--input_top_zip_path", required=True) 

272 required_args.add_argument("--output_top_zip_path", required=True) 

273 parser.add_argument("--input_ndx_path", required=False) 

274 

275 args = parser.parse_args() 

276 config = args.config if args.config else None 

277 properties = settings.ConfReader(config=config).get_prop_dic() 

278 

279 # Specific call of each building block 

280 genion( 

281 input_tpr_path=args.input_tpr_path, 

282 output_gro_path=args.output_gro_path, 

283 input_top_zip_path=args.input_top_zip_path, 

284 output_top_zip_path=args.output_top_zip_path, 

285 input_ndx_path=args.input_ndx_path, 

286 properties=properties, 

287 ) 

288 

289 

290if __name__ == "__main__": 

291 main()