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

89 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-25 09:23 +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: 2024.5 

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( 

130 f"{self.replaced_group}" 

131 ) 

132 self.stage_files() 

133 

134 # Unzip topology to topology_out 

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

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

137 

138 if self.container_path: 

139 shutil.copytree( 

140 top_dir, 

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

142 Path(top_dir).name 

143 ), 

144 ) 

145 top_file = str( 

146 Path(self.container_volume_path).joinpath( 

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

148 ) 

149 ) 

150 

151 self.cmd = [ 

152 str(self.binary_path), 

153 "genion", 

154 "-s", 

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

156 "-o", 

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

158 "-p", 

159 top_file, 

160 ] 

161 

162 if ( 

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

164 ): 

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

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

167 

168 if self.neutral: 

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

170 

171 if self.concentration: 

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

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

174 fu.log( 

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

176 self.out_log, 

177 self.global_log, 

178 ) 

179 

180 if self.seed is not None: 

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

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

183 

184 # Add stdin input file 

185 self.cmd.append("<") 

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

187 

188 if self.gmx_lib: 

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

190 

191 # Run Biobb block 

192 self.run_biobb() 

193 

194 # Copy files to host 

195 self.copy_to_host() 

196 

197 if self.container_path: 

198 top_file = str( 

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

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

201 ) 

202 ) 

203 

204 # zip topology 

205 fu.log( 

206 "Compressing topology to: %s" 

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

208 self.out_log, 

209 self.global_log, 

210 ) 

211 fu.zip_top( 

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

213 top_file=top_file, 

214 out_log=self.out_log, 

215 remove_original_files=self.remove_tmp 

216 ) 

217 

218 # Remove temporal files 

219 self.tmp_files.extend( 

220 [ 

221 # str(self.stage_io_dict.get("unique_dir", "")), 

222 top_dir, 

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

224 ] 

225 ) 

226 self.remove_tmp_files() 

227 

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

229 return self.return_code 

230 

231 

232def genion( 

233 input_tpr_path: Union[str, Path], 

234 output_gro_path: Union[str, Path], 

235 input_top_zip_path: Union[str, Path], 

236 output_top_zip_path: Union[str, Path], 

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

238 properties: Optional[dict] = None, 

239 **kwargs, 

240) -> int: 

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

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

243 return Genion( 

244 input_tpr_path=input_tpr_path, 

245 output_gro_path=output_gro_path, 

246 input_top_zip_path=input_top_zip_path, 

247 output_top_zip_path=output_top_zip_path, 

248 input_ndx_path=input_ndx_path, 

249 properties=properties, 

250 **kwargs, 

251 ).launch() 

252 

253 

254genion.__doc__ = Genion.__doc__ 

255 

256 

257def main(): 

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

259 parser = argparse.ArgumentParser( 

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

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

262 ) 

263 parser.add_argument( 

264 "-c", 

265 "--config", 

266 required=False, 

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

268 ) 

269 

270 # Specific args of each building block 

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

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

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

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

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

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

277 

278 args = parser.parse_args() 

279 config = args.config if args.config else None 

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

281 

282 # Specific call of each building block 

283 genion( 

284 input_tpr_path=args.input_tpr_path, 

285 output_gro_path=args.output_gro_path, 

286 input_top_zip_path=args.input_top_zip_path, 

287 output_top_zip_path=args.output_top_zip_path, 

288 input_ndx_path=args.input_ndx_path, 

289 properties=properties, 

290 ) 

291 

292 

293if __name__ == "__main__": 

294 main()