Coverage for biobb_amber / leap / leap_add_ions.py: 59%

219 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-22 10:27 +0000

1#!/usr/bin/env python3 

2 

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

4 

5import os 

6import re 

7import shutil 

8from decimal import Decimal 

9from pathlib import PurePath 

10from typing import List, Optional 

11 

12from biobb_common.generic.biobb_object import BiobbObject 

13from biobb_common.tools import file_utils as fu 

14from biobb_common.tools.file_utils import launchlogger 

15 

16from biobb_amber.leap.common import _from_string_to_list 

17 

18 

19class LeapAddIons(BiobbObject): 

20 """ 

21 | biobb_amber LeapAddIons 

22 | Wrapper of the `AmberTools (AMBER MD Package) leap tool <https://ambermd.org/AmberTools.php>`_ module. 

23 | Adds counterions to a system box for an AMBER MD system using tLeap tool from the AmberTools MD package. 

24 

25 Args: 

26 input_pdb_path (str): Input 3D structure PDB file. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/structure.solv.pdb>`_. Accepted formats: pdb (edam:format_1476). 

27 input_lib_path (str) (Optional): Input ligand library parameters file. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/ligand.lib>`_. Accepted formats: lib (edam:format_3889), zip (edam:format_3987). 

28 input_frcmod_path (str) (Optional): Input ligand frcmod parameters file. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/ligand.frcmod>`_. Accepted formats: frcmod (edam:format_3888), zip (edam:format_3987). 

29 input_params_path (str) (Optional): Additional leap parameter files to load with loadAmberParams Leap command. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/frcmod.ionsdang_spce.txt>`_. Accepted formats: in (edam:format_2330), leapin (edam:format_2330), txt (edam:format_2330), zip (edam:format_3987). 

30 input_prep_path (str) (Optional): Additional leap parameter files to load with loadAmberPrep Leap command. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/frcmod.ionsdang_spce.txt>`_. Accepted formats: in (edam:format_2330), leapin (edam:format_2330), txt (edam:format_2330), zip (edam:format_3987). 

31 input_source_path (str) (Optional): Additional leap command files to load with source Leap command. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/leap/leaprc.water.spce.txt>`_. Accepted formats: in (edam:format_2330), leapin (edam:format_2330), txt (edam:format_2330), zip (edam:format_3987). 

32 output_pdb_path (str): Output 3D structure PDB file matching the topology file. File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/leap/structure.ions.pdb>`_. Accepted formats: pdb (edam:format_1476). 

33 output_top_path (str): Output topology file (AMBER ParmTop). File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/leap/structure.ions.top>`_. Accepted formats: top (edam:format_3881), parmtop (edam:format_3881), prmtop (edam:format_3881). 

34 output_crd_path (str): Output coordinates file (AMBER crd). File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/leap/structure.ions.crd>`_. Accepted formats: crd (edam:format_3878), mdcrd (edam:format_3878), inpcrd (edam:format_3878). 

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

36 * **forcefield** (*list*) - (["protein.ff14SB","DNA.bsc1","gaff"]) Forcefields to be used for the structure generation. Each item should be either a path to a leaprc file or a string with the leaprc file name if the force field is included with Amber (e.g. "/path/to/leaprc.protein.ff14SB" or "protein.ff14SB"). Default values: ["protein.ff14SB","DNA.bsc1","gaff"]. 

37 * **water_type** (*str*) - ("TIP3PBOX") Water molecule parameters to be used for the topology. Values: POL3BOX, QSPCFWBOX, SPCBOX, SPCFWBOX, TIP3PBOX, TIP3PFBOX, TIP4PBOX, TIP4PEWBOX, OPCBOX, OPC3BOX, TIP5PBOX. 

38 * **box_type** (*str*) - ("truncated_octahedron") Type for the MD system box. Values: cubic, truncated_octahedron. 

39 * **ions_type** (*str*) - ("ionsjc_tip3p") Ions type. Values: ionsjc_tip3p, ionsjc_spce, ionsff99_tip3p, ions_charmm22, ionsjc_tip4pew, None. 

40 * **neutralise** (*bool*) - ("True") Energetically neutralise the system adding the necessary counterions. 

41 * **ionic_concentration** (*float*) - (50) Additional ionic concentration to include in the system box. Units in mM/L. 

42 * **positive_ions_number** (*int*) - (0) Number of additional positive ions to include in the system box. 

43 * **negative_ions_number** (*int*) - (0) Number of additional negative ions to include in the system box. 

44 * **positive_ions_type** (*str*) - ("Na+") Type of additional positive ions to include in the system box. Values: Na+,K+. 

45 * **negative_ions_type** (*str*) - ("Cl-") Type of additional negative ions to include in the system box. Values: Cl-. 

46 * **binary_path** (*str*) - ("tleap") Path to the tleap executable binary. 

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

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

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

50 * **container_path** (*str*) - (None) Container path definition. 

51 * **container_image** (*str*) - ('afandiadib/ambertools:serial') Container image definition. 

52 * **container_volume_path** (*str*) - ('/tmp') Container volume path definition. 

53 * **container_working_dir** (*str*) - (None) Container working directory definition. 

54 * **container_user_id** (*str*) - (None) Container user_id definition. 

55 * **container_shell_path** (*str*) - ('/bin/bash') Path to default shell inside the container. 

56 

57 Examples: 

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

59 

60 from biobb_amber.leap.leap_add_ions import leap_add_ions 

61 prop = { 

62 'forcefield': ['protein.ff14SB'], 

63 'water_type': 'TIP3PBOX', 

64 'neutralise' : True 

65 } 

66 leap_add_ions(input_pdb_path='/path/to/structure.pdb', 

67 output_pdb_path='/path/to/newStructure.pdb', 

68 output_top_path='/path/to/newTopology.top', 

69 output_crd_path='/path/to/newCoordinates.crd', 

70 properties=prop) 

71 

72 Info: 

73 * wrapped_software: 

74 * name: AmberTools tLeap 

75 * version: >20.9 

76 * license: LGPL 2.1 

77 * ontology: 

78 * name: EDAM 

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

80 

81 """ 

82 

83 def __init__( 

84 self, 

85 input_pdb_path: str, 

86 output_pdb_path: str, 

87 output_top_path: str, 

88 output_crd_path: str, 

89 input_lib_path: Optional[str] = None, 

90 input_frcmod_path: Optional[str] = None, 

91 input_params_path: Optional[str] = None, 

92 input_prep_path: Optional[str] = None, 

93 input_source_path: Optional[str] = None, 

94 properties: Optional[dict] = None, 

95 **kwargs, 

96 ): 

97 properties = properties or {} 

98 

99 # Call parent class constructor 

100 super().__init__(properties) 

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

102 

103 # Input/Output files 

104 self.io_dict = { 

105 "in": { 

106 "input_pdb_path": input_pdb_path, 

107 "input_lib_path": input_lib_path, 

108 "input_frcmod_path": input_frcmod_path, 

109 "input_params_path": input_params_path, 

110 "input_prep_path": input_prep_path, 

111 "input_source_path": input_source_path, 

112 }, 

113 "out": { 

114 "output_pdb_path": output_pdb_path, 

115 "output_top_path": output_top_path, 

116 "output_crd_path": output_crd_path, 

117 }, 

118 } 

119 

120 # Ligand Parameter lists 

121 self.ligands_lib_list = [] 

122 if input_lib_path: 

123 self.ligands_lib_list.append(input_lib_path) 

124 

125 self.ligands_frcmod_list = [] 

126 if input_frcmod_path: 

127 self.ligands_frcmod_list.append(input_frcmod_path) 

128 

129 # Set default forcefields 

130 amber_home_path = os.getenv("AMBERHOME") 

131 protein_ff14SB_path = os.path.join(amber_home_path, 'dat', 'leap', 'cmd', 'leaprc.protein.ff14SB') 

132 dna_bsc1_path = os.path.join(amber_home_path, 'dat', 'leap', 'cmd', 'leaprc.DNA.bsc1') 

133 gaff_path = os.path.join(amber_home_path, 'dat', 'leap', 'cmd', 'leaprc.gaff') 

134 

135 # Properties specific for BB 

136 self.properties = properties 

137 self.forcefield = _from_string_to_list( 

138 properties.get("forcefield", [protein_ff14SB_path, dna_bsc1_path, gaff_path]) 

139 ) 

140 # Find the paths of the leaprc files if only the force field names are provided 

141 self.forcefield = self.find_leaprc_paths(self.forcefield) 

142 self.water_type = properties.get("water_type", "TIP3PBOX") 

143 self.box_type = properties.get("box_type", "truncated_octahedron") 

144 self.ions_type = properties.get("ions_type", "ionsjc_tip3p") 

145 self.neutralise = properties.get("neutralise", True) 

146 self.ionic_concentration = properties.get("ionic_concentration", 50) 

147 self.positive_ions_number = properties.get("positive_ions_number", 0) 

148 self.positive_ions_type = properties.get("positive_ions_type", "Na+") 

149 self.negative_ions_number = properties.get("negative_ions_number", 0) 

150 self.negative_ions_type = properties.get("negative_ions_type", "Cl-") 

151 self.binary_path = properties.get("binary_path", "tleap") 

152 

153 # Check the properties 

154 self.check_properties(properties) 

155 self.check_arguments() 

156 

157 def find_leaprc_paths(self, forcefields: List[str]) -> List[str]: 

158 """ 

159 Find the leaprc paths for the force fields provided. 

160 

161 For each item in the forcefields list, the function checks if the str is a path to an existing file. 

162 If not, it tries to find the file in the $AMBERHOME/dat/leap/cmd/ directory or the $AMBERHOME/dat/leap/cmd/oldff/ 

163 directory with and without the leaprc prefix. 

164 

165 Args: 

166 forcefields (List[str]): List of force fields to find the leaprc files for. 

167 

168 Returns: 

169 List[str]: List of leaprc file paths. 

170 """ 

171 

172 leaprc_paths = [] 

173 

174 for forcefield in forcefields: 

175 

176 num_paths = len(leaprc_paths) 

177 

178 # Check if the forcefield is a path to an existing file 

179 if os.path.exists(forcefield): 

180 leaprc_paths.append(forcefield) 

181 continue 

182 

183 # Check if the forcefield is in the leaprc directory 

184 leaprc_path = os.path.join(os.environ.get('AMBERHOME', ''), 'dat', 'leap', 'cmd', f"leaprc.{forcefield}") 

185 if os.path.exists(leaprc_path): 

186 leaprc_paths.append(leaprc_path) 

187 continue 

188 

189 # Check if the forcefield is in the oldff directory 

190 leaprc_path = os.path.join(os.environ.get('AMBERHOME', ''), 'dat', 'leap', 'cmd', 'oldff', f"leaprc.{forcefield}") 

191 if os.path.exists(leaprc_path): 

192 leaprc_paths.append(leaprc_path) 

193 continue 

194 

195 # Check if the forcefield is in the leaprc directory without the leaprc prefix 

196 leaprc_path = os.path.join(os.environ.get('AMBERHOME', ''), 'dat', 'leap', 'cmd', f"{forcefield}") 

197 if os.path.exists(leaprc_path): 

198 leaprc_paths.append(leaprc_path) 

199 continue 

200 

201 # Check if the forcefield is in the oldff directory without the leaprc prefix 

202 leaprc_path = os.path.join(os.environ.get('AMBERHOME', ''), 'dat', 'leap', 'cmd', 'oldff', f"{forcefield}") 

203 if os.path.exists(leaprc_path): 

204 leaprc_paths.append(leaprc_path) 

205 continue 

206 

207 new_num_paths = len(leaprc_paths) 

208 

209 if new_num_paths == num_paths: 

210 raise ValueError(f"Force field {forcefield} not found. Check the $AMBERHOME/dat/leap/cmd/ directory for available force fields or provide the path to an existing leaprc file.") 

211 

212 return leaprc_paths 

213 

214 # def check_data_params(self, out_log, err_log): 

215 # """ Checks input/output paths correctness """ 

216 

217 # # Check input(s) 

218 # self.io_dict["in"]["input_pdb_path"] = check_input_path(self.io_dict["in"]["input_pdb_path"], "input_pdb_path", False, out_log, self.__class__.__name__) 

219 # self.io_dict["in"]["input_lib_path"] = check_input_path(self.io_dict["in"]["input_lib_path"], "input_lib_path", True, out_log, self.__class__.__name__) 

220 # self.io_dict["in"]["input_frcmod_path"] = check_input_path(self.io_dict["in"]["input_frcmod_path"], "input_frcmod_path", True, out_log, self.__class__.__name__) 

221 # # self.io_dict["in"]["input_params_path"] = check_input_path(self.io_dict["in"]["input_params_path"], "input_params_path", True, out_log, self.__class__.__name__) 

222 # # self.io_dict["in"]["input_source_path"] = check_input_path(self.io_dict["in"]["input_source_path"], "input_source_path", True, out_log, self.__class__.__name__) 

223 

224 # # Check output(s) 

225 # self.io_dict["out"]["output_pdb_path"] = check_output_path(self.io_dict["out"]["output_pdb_path"], "output_pdb_path", False, out_log, self.__class__.__name__) 

226 # self.io_dict["out"]["output_top_path"] = check_output_path(self.io_dict["out"]["output_top_path"], "output_top_path", False, out_log, self.__class__.__name__) 

227 # self.io_dict["out"]["output_crd_path"] = check_output_path(self.io_dict["out"]["output_crd_path"], "output_crd_path", False, out_log, self.__class__.__name__) 

228 

229 def find_out_number_of_ions(self): 

230 """Computes the number of positive and negative ions from the input ionic 

231 concentration and the number of water molecules in the system box.""" 

232 

233 # Finding number of water molecules in the box 

234 cnt = 0 

235 with open(self.io_dict["in"]["input_pdb_path"]) as fp: 

236 for line in fp: 

237 # Water molecules are identified with different resids 

238 # by different forcefields / MD packages 

239 pq = re.compile((r"WAT|HOH|TP3|TIP3|SOL"), re.M) 

240 if pq.search(line): 

241 # Incrementing number of water molecules just in the water 

242 # oxygen atom, ignoring hydrogen or dummy atoms 

243 pq2 = re.compile(r"H|EPW|EP1|EP2", re.M) 

244 if not pq2.search(line): 

245 cnt = cnt + 1 

246 

247 # To get to X mM ions we need 

248 # n(NaCl) = #waters / 55 Mol * X M 

249 # where X M = X mM / 1000 

250 self.nio = int((cnt / 55) * (self.ionic_concentration / 1000)) 

251 

252 @launchlogger 

253 def launch(self): 

254 """Launches the execution of the LeapAddIons module.""" 

255 

256 # check input/output paths and parameters 

257 # self.check_data_params(self.out_log, self.err_log) 

258 

259 # Setup Biobb 

260 if self.check_restart(): 

261 return 0 

262 self.stage_files() 

263 

264 # Water Type 

265 # leaprc.water.tip4pew, tip4pd, tip3p, spceb, spce, opc, fb4, fb3 

266 # Values: POL3BOX, QSPCFWBOX, SPCBOX, SPCFWBOX, TIP3PBOX, TIP3PFBOX, TIP4PBOX, TIP4PEWBOX, OPCBOX, OPC3BOX, TIP5PBOX. 

267 source_wat_command = "source leaprc.water.tip3p" 

268 if self.water_type == "TIP4PEWBOX": 

269 source_wat_command = "leaprc.water.tip4pew" 

270 if self.water_type == "TIP4PBOX": 

271 source_wat_command = "leaprc.water.tip4pd" 

272 if re.match(r"SPC", self.water_type): 

273 source_wat_command = "source leaprc.water.spce" 

274 if re.match(r"OPC", self.water_type): 

275 source_wat_command = "source leaprc.water.opc" 

276 

277 # Counterions 

278 ions_command = "" 

279 if self.neutralise: 

280 # ions_command = ions_command + "addions mol " + self.negative_ions_type + " 0 \n" 

281 # ions_command = ions_command + "addions mol " + self.positive_ions_type + " 0 \n" 

282 ions_command = ( 

283 ions_command + "addionsRand mol " + self.negative_ions_type + " 0 \n" 

284 ) 

285 ions_command = ( 

286 ions_command + "addionsRand mol " + self.positive_ions_type + " 0 \n" 

287 ) 

288 

289 if ( 

290 self.ionic_concentration and self.negative_ions_number == 0 and self.positive_ions_number == 0 

291 ): 

292 self.find_out_number_of_ions() 

293 nneg = self.nio # Update with function 

294 npos = self.nio # Update with function 

295 # ions_command = ions_command + "addions mol " + self.negative_ions_type + " " + str(nneg) + " \n" 

296 # ions_command = ions_command + "addions mol " + self.positive_ions_type + " " + str(npos) + " \n" 

297 ions_command = ( 

298 ions_command + "addionsRand mol " + self.negative_ions_type + " " + str(nneg) + " \n" 

299 ) 

300 ions_command = ( 

301 ions_command + "addionsRand mol " + self.positive_ions_type + " " + str(npos) + " \n" 

302 ) 

303 else: 

304 if self.negative_ions_number != 0: 

305 # ions_command = ions_command + "addions mol " + self.negative_ions_type + " " + str(self.negative_ions_number) + " \n" 

306 ions_command = ( 

307 ions_command + "addionsRand mol " + self.negative_ions_type + " " + str(self.negative_ions_number) + " \n" 

308 ) 

309 if self.positive_ions_number != 0: 

310 # ions_command = ions_command + "addions mol " + self.positive_ions_type + " " + str(self.positive_ions_number) + " \n" 

311 ions_command = ( 

312 ions_command + "addionsRand mol " + self.positive_ions_type + " " + str(self.positive_ions_number) + " \n" 

313 ) 

314 

315 # Creating temporary folder & Leap configuration (instructions) file 

316 if self.container_path: 

317 instructions_file = str( 

318 PurePath(self.stage_io_dict["unique_dir"]).joinpath("leap.in") 

319 ) 

320 instructions_file_path = str( 

321 PurePath(self.container_volume_path).joinpath("leap.in") 

322 ) 

323 tmp_folder = None 

324 else: 

325 tmp_folder = fu.create_unique_dir() 

326 instructions_file = str(PurePath(tmp_folder).joinpath("leap.in")) 

327 fu.log("Creating %s temporary folder" % tmp_folder, self.out_log) 

328 instructions_file_path = instructions_file 

329 

330 ligands_lib_list = [] 

331 if self.io_dict["in"]["input_lib_path"] is not None: 

332 if self.io_dict["in"]["input_lib_path"].endswith(".zip"): 

333 ligands_lib_list = fu.unzip_list( 

334 self.stage_io_dict["in"]["input_lib_path"], 

335 dest_dir=tmp_folder, 

336 out_log=self.out_log, 

337 ) 

338 else: 

339 ligands_lib_list.append(self.stage_io_dict["in"]["input_lib_path"]) 

340 

341 ligands_frcmod_list = [] 

342 if self.io_dict["in"]["input_frcmod_path"] is not None: 

343 if self.io_dict["in"]["input_frcmod_path"].endswith(".zip"): 

344 ligands_frcmod_list = fu.unzip_list( 

345 self.stage_io_dict["in"]["input_frcmod_path"], 

346 dest_dir=tmp_folder, 

347 out_log=self.out_log, 

348 ) 

349 else: 

350 ligands_frcmod_list.append( 

351 self.stage_io_dict["in"]["input_frcmod_path"] 

352 ) 

353 

354 amber_params_list = [] 

355 if self.io_dict["in"]["input_params_path"] is not None: 

356 if self.io_dict["in"]["input_params_path"].endswith(".zip"): 

357 amber_params_list = fu.unzip_list( 

358 self.stage_io_dict["in"]["input_params_path"], 

359 dest_dir=tmp_folder, 

360 out_log=self.out_log, 

361 ) 

362 else: 

363 amber_params_list.append(self.stage_io_dict["in"]["input_params_path"]) 

364 

365 amber_prep_list = [] 

366 if self.io_dict["in"]["input_prep_path"] is not None: 

367 if self.io_dict["in"]["input_prep_path"].endswith(".zip"): 

368 amber_prep_list = fu.unzip_list( 

369 self.stage_io_dict["in"]["input_prep_path"], 

370 dest_dir=tmp_folder, 

371 out_log=self.out_log, 

372 ) 

373 else: 

374 amber_prep_list.append(self.stage_io_dict["in"]["input_prep_path"]) 

375 

376 leap_source_list = [] 

377 if self.io_dict["in"]["input_source_path"] is not None: 

378 if self.io_dict["in"]["input_source_path"].endswith(".zip"): 

379 leap_source_list = fu.unzip_list( 

380 self.stage_io_dict["in"]["input_source_path"], 

381 dest_dir=tmp_folder, 

382 out_log=self.out_log, 

383 ) 

384 else: 

385 leap_source_list.append(self.stage_io_dict["in"]["input_source_path"]) 

386 

387 # instructions_file = str(PurePath(tmp_folder).joinpath("leap.in")) 

388 with open(instructions_file, "w") as leapin: 

389 # Forcefields loaded by default: 

390 # Protein: ff14SB (PARM99 + frcmod.ff99SB + frcmod.parmbsc0 + OL3 for RNA) 

391 # leapin.write("source leaprc.protein.ff14SB \n") 

392 # DNA: parmBSC1 (ParmBSC1 (ff99 + bsc0 + bsc1) for DNA. Ivani et al. Nature Methods 13: 55, 2016) 

393 # leapin.write("source leaprc.DNA.bsc1 \n") 

394 # Ligands: GAFF (General Amber Force field, J. Comput. Chem. 2004 Jul 15;25(9):1157-74) 

395 # leapin.write("source leaprc.gaff \n") 

396 

397 # Forcefields loaded from input forcefield property 

398 for t in self.forcefield: 

399 leapin.write("source {}\n".format(t)) 

400 

401 # Additional Leap commands 

402 for leap_commands in leap_source_list: 

403 leapin.write("source " + leap_commands + "\n") 

404 

405 # Water Model loaded from input water_model property 

406 leapin.write(source_wat_command + " \n") 

407 

408 # Ions Type 

409 if self.ions_type != "None": 

410 leapin.write("loadamberparams frcmod." + self.ions_type + "\n") 

411 

412 # Additional Amber parameters 

413 for amber_params in amber_params_list: 

414 leapin.write("loadamberparams " + amber_params + "\n") 

415 

416 # Additional Amber prep files 

417 for amber_prep in amber_prep_list: 

418 leapin.write("loadamberprep " + amber_prep + "\n") 

419 

420 # Ligand(s) libraries (if any) 

421 for amber_lib in ligands_lib_list: 

422 leapin.write("loadOff " + amber_lib + "\n") 

423 for amber_frcmod in ligands_frcmod_list: 

424 leapin.write("loadamberparams " + amber_frcmod + "\n") 

425 

426 # Loading PDB file 

427 leapin.write( 

428 "mol = loadpdb " + self.stage_io_dict["in"]["input_pdb_path"] + " \n" 

429 ) 

430 

431 # Adding ions 

432 leapin.write(ions_command) 

433 

434 # Generating box 

435 leapin.write("setBox mol vdw \n") 

436 

437 # Saving output PDB file, coordinates and topology 

438 leapin.write( 

439 "savepdb mol " + self.stage_io_dict["out"]["output_pdb_path"] + " \n" 

440 ) 

441 leapin.write( 

442 "saveAmberParm mol " + self.stage_io_dict["out"]["output_top_path"] + " " + self.stage_io_dict["out"]["output_crd_path"] + "\n" 

443 ) 

444 leapin.write("quit \n") 

445 

446 # Command line 

447 self.cmd = [self.binary_path, "-f", instructions_file_path] 

448 

449 # Run Biobb block 

450 self.run_biobb() 

451 

452 # Copy files to host 

453 self.copy_to_host() 

454 

455 if self.box_type != "cubic": 

456 fu.log( 

457 "Fixing truncated octahedron Box in the topology and coordinates files", 

458 self.out_log, 

459 self.global_log, 

460 ) 

461 

462 # Taking box info from input PDB file, CRYST1 tag (first line) 

463 with open(self.io_dict["in"]["input_pdb_path"]) as file: 

464 lines = file.readlines() 

465 pdb_line = lines[0] 

466 

467 if "OCTBOX" not in pdb_line: 

468 fu.log( 

469 "WARNING: box info not found in input PDB file (OCTBOX). Needed to correctly assign the octahedron box. Assuming cubic box.", 

470 self.out_log, 

471 self.global_log, 

472 ) 

473 else: 

474 # PDB info: CRYST1 86.316 86.316 86.316 109.47 109.47 109.47 P 1 

475 # PDB info: OCTBOX 86.1942924 86.1942924 86.1942924 109.4712190 109.4712190 109.4712190 

476 # regex_box = 'CRYST1\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*P 1' 

477 regex_box = r"OCTBOX\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)\s*(\d+\.\d+)" 

478 box = re.findall(regex_box, pdb_line)[0] 

479 box_line = "" 

480 for coord in box: 

481 box_line += "{:12.7f}".format(float(coord)) 

482 

483 # PRMTOP info: 1.09471219E+02 8.63157502E+01 8.63157502E+01 8.63157502E+01 

484 top_box_line = "" 

485 top_box_line += " %.8E" % Decimal(float(box[3])) 

486 top_box_line += " %.8E" % Decimal(float(box[0])) 

487 top_box_line += " %.8E" % Decimal(float(box[1])) 

488 top_box_line += " %.8E" % Decimal(float(box[2])) 

489 

490 # Removing box generated by tleap from the crd file (last line) 

491 with open(self.io_dict["out"]["output_crd_path"]) as file: 

492 lines = file.readlines() 

493 crd_lines = lines[:-1] 

494 

495 # Adding old box coordinates (taken from the input pdb) 

496 crd_lines.append(box_line) 

497 

498 with open(self.io_dict["out"]["output_crd_path"], "w") as file: 

499 for line in crd_lines: 

500 file.write(str(line)) 

501 file.write("\n") 

502 

503 # Now fixing IFBOX param in prmtop. 

504 box_flag = False 

505 ifbox_flag = 0 

506 # %FLAG BOX_DIMENSIONS 

507 # %FORMAT(5E16.8) 

508 # 1.09471219E+02 8.63157502E+01 8.63157502E+01 8.63157502E+01 

509 

510 tmp_parmtop = str( 

511 PurePath(str(tmp_folder)).joinpath("top_temp.parmtop") 

512 ) 

513 shutil.copyfile(self.io_dict["out"]["output_top_path"], tmp_parmtop) 

514 

515 with open(self.io_dict["out"]["output_top_path"], "w") as new_top: 

516 with open(tmp_parmtop, "r") as old_top: 

517 for line in old_top: 

518 if "BOX_DIMENSIONS" in line: 

519 box_flag = True 

520 new_top.write(line) 

521 elif box_flag and "FORMAT" not in line: 

522 new_top.write(top_box_line + "\n") 

523 box_flag = False 

524 elif ( 

525 "FLAG POINTERS" in line or ifbox_flag == 1 or ifbox_flag == 2 or ifbox_flag == 3 

526 ): 

527 ifbox_flag += 1 

528 new_top.write(line) 

529 elif ifbox_flag == 4: 

530 # new_top.write(top_box_line + "\n") 

531 new_top.write(line[:56] + " 2" + line[64:]) 

532 ifbox_flag += 1 

533 else: 

534 new_top.write(line) 

535 

536 # remove temporary folder(s) 

537 self.tmp_files.extend([str(tmp_folder), "leap.log"]) 

538 self.remove_tmp_files() 

539 

540 self.check_arguments(output_files_created=True, raise_exception=False) 

541 

542 return self.return_code 

543 

544 

545def leap_add_ions( 

546 input_pdb_path: str, 

547 output_pdb_path: str, 

548 output_top_path: str, 

549 output_crd_path: str, 

550 input_lib_path: Optional[str] = None, 

551 input_frcmod_path: Optional[str] = None, 

552 input_params_path: Optional[str] = None, 

553 input_prep_path: Optional[str] = None, 

554 input_source_path: Optional[str] = None, 

555 properties: Optional[dict] = None, 

556 **kwargs, 

557) -> int: 

558 """Create the :class:`LeapAddIons <leap.leap_add_ions.LeapAddIons>` class and 

559 execute the :meth:`launch() <leap.leap_add_ions.LeapAddIons.launch>` method.""" 

560 return LeapAddIons(**dict(locals())).launch() 

561 

562 

563leap_add_ions.__doc__ = LeapAddIons.__doc__ 

564main = LeapAddIons.get_main(leap_add_ions, "Adds counterions to a system box for an AMBER MD system using tLeap.") 

565 

566if __name__ == "__main__": 

567 main()