Coverage for biobb_pmx/pmxbiobb/pmxgentop.py: 91%

78 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 06:59 +0000

1#!/usr/bin/env python3 

2 

3"""Module containing the PMX gentop class and the command line interface.""" 

4 

5import os 

6import shutil 

7import sys 

8from pathlib import Path 

9from typing import Optional 

10 

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 

15 

16class Pmxgentop(BiobbObject): 

17 """ 

18 | biobb_pmx Pmxgentop 

19 | Wrapper class for the `PMX gentop <https://github.com/deGrootLab/pmx>`_ module. 

20 | Generate a topology file for a morphing simulation. 

21 

22 Args: 

23 input_top_zip_path (str): Path the input GROMACS topology TOP and ITP files in zip format. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/blob/master/biobb_pmx/test/data/pmx/mut_gmx.top.zip>`_. Accepted formats: zip (edam:format_3987). 

24 output_top_zip_path (str): Path the output TOP topology in zip format. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_output_topology.zip>`_. Accepted formats: zip (edam:format_3987). 

25 properties (dic): 

26 * **force_field** (*str*) - ("amber99sb-star-ildn-mut") Force field to use. If **input_top_zip_path** is a top file, it's not necessary to specify the forcefield, as it will be determined automatically. If **input_top_zip_path** is an itp file, then it's needed. 

27 * **split** (*bool*) - (False) Write separate topologies for the vdW and charge transformations. 

28 * **scale_mass** (*bool*) - (False) Scale the masses of morphing atoms so that dummies have a mass of 1. 

29 * **gmx_lib** (*str*) - ("$CONDA_PREFIX/lib/python3.7/site-packages/pmx/data/mutff/") Path to the GMXLIB folder in your computer. 

30 * **binary_path** (*str*) - ("pmx") Path to the PMX command line interface. 

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

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

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

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

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

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

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

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

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

40 

41 Examples: 

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

43 

44 from biobb_pmx.pmxbiobb.pmxgentop import pmxgentop 

45 prop = { 

46 'gmx_lib': '/path/to/myGMXLIB/', 

47 'force_field': 'amber99sb-star-ildn-mut' 

48 } 

49 pmxgentop(input_top_zip_path='/path/to/myTopology.zip', 

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

51 properties=prop) 

52 

53 Info: 

54 * wrapped_software: 

55 * name: PMX gentop 

56 * version: >=1.0.1 

57 * license: GNU 

58 * ontology: 

59 * name: EDAM 

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

61 

62 """ 

63 

64 def __init__( 

65 self, 

66 input_top_zip_path: str, 

67 output_top_zip_path: str, 

68 properties: Optional[dict] = None, 

69 **kwargs, 

70 ) -> None: 

71 properties = properties or {} 

72 

73 # Call parent class constructor 

74 super().__init__(properties) 

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

76 

77 # Input/Output files 

78 self.io_dict = {"in": {}, "out": {"output_top_zip_path": output_top_zip_path}} 

79 # Should not be copied inside container 

80 self.input_top_zip_path = input_top_zip_path 

81 

82 # Properties specific for BB 

83 self.force_field = properties.get("force_field", "amber99sb-star-ildn-mut") 

84 self.split = properties.get("split", False) 

85 self.scale_mass = properties.get("scale_mass", False) 

86 

87 # Properties common in all PMX BB 

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

89 if not self.gmx_lib and os.environ.get("CONDA_PREFIX", ""): 

90 python_version = f"{sys.version_info.major}.{sys.version_info.minor}" 

91 self.gmx_lib = str( 

92 Path(os.environ.get("CONDA_PREFIX", "")).joinpath( 

93 f"lib/python{python_version}/site-packages/pmx/data/mutff/" 

94 ) 

95 ) 

96 if properties.get("container_path"): 

97 self.gmx_lib = str( 

98 Path("/usr/local/").joinpath( 

99 "lib/python3.8/site-packages/pmx/data/mutff/" 

100 ) 

101 ) 

102 self.binary_path = properties.get("binary_path", "pmx") 

103 

104 # Check the properties 

105 self.check_properties(properties) 

106 self.check_arguments() 

107 

108 @launchlogger 

109 def launch(self) -> int: 

110 """Execute the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` pmx.pmxgentop.Pmxgentop object.""" 

111 

112 # Setup Biobb 

113 if self.check_restart(): 

114 return 0 

115 self.stage_files() 

116 

117 if self.container_path: 

118 working_dir = self.container_volume_path if self.container_volume_path else "/data" 

119 else: 

120 working_dir = self.stage_io_dict.get("unique_dir", "") 

121 

122 # Check if executable exists 

123 if not self.container_path: 

124 if not Path(self.binary_path).is_file(): 

125 if not shutil.which(self.binary_path): 

126 raise FileNotFoundError( 

127 "Executable %s not found. Check if it is installed in your system and correctly defined in the properties" 

128 % self.binary_path 

129 ) 

130 

131 # Unzip topology to topology_out 

132 top_file = str( 

133 Path( 

134 fu.unzip_top(zip_file=self.input_top_zip_path, out_log=self.out_log) 

135 ).resolve() 

136 ) 

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

138 

139 # Copy extra files to sandbox: topology folder 

140 top_dir_in_sandbox = Path(self.stage_io_dict.get("unique_dir", "")).joinpath( 

141 Path(top_dir).name 

142 ) 

143 shutil.copytree(top_dir, str(top_dir_in_sandbox)) 

144 top_file = str(Path(Path(top_dir).name).joinpath(Path(top_file).name)) 

145 

146 output_file_name = fu.create_name( 

147 prefix=self.prefix, step=self.step, name=str(Path(top_file).name) 

148 ) 

149 unique_dir_output_file = str( 

150 Path(self.stage_io_dict.get("unique_dir", "")).joinpath(output_file_name) 

151 ) 

152 fu.log(f"unique_dir_output_file: {unique_dir_output_file}", self.out_log) 

153 

154 self.cmd = [ 

155 "cd", 

156 working_dir, 

157 ";", 

158 self.binary_path, 

159 "gentop", 

160 "-o", 

161 str(Path(output_file_name)), 

162 "-ff", 

163 self.force_field, 

164 "-p", 

165 top_file, 

166 ] 

167 

168 if self.split: 

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

170 if self.scale_mass: 

171 self.cmd.append("--scale_mass") 

172 

173 if self.gmx_lib: 

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

175 

176 # Run Biobb block 

177 self.run_biobb() 

178 

179 # Copy files to host 

180 self.copy_to_host() 

181 

182 # Remove paths from top file 

183 with open(Path(unique_dir_output_file)) as top_fh: 

184 top_lines = top_fh.readlines() 

185 with open(Path(unique_dir_output_file), "w") as top_fh: 

186 for line in top_lines: 

187 top_fh.write( 

188 line.replace(str(Path(unique_dir_output_file).parent) + "/", "") 

189 ) 

190 # Copy the not modified itp files 

191 for orig_itp_file in Path(top_dir).iterdir(): 

192 fu.log( 

193 f"Check if {str(Path(unique_dir_output_file).parent.joinpath(Path(orig_itp_file).name))} exists", 

194 self.out_log, 

195 self.global_log, 

196 ) 

197 if ( 

198 not Path(unique_dir_output_file) 

199 .parent.joinpath(Path(orig_itp_file).name) 

200 .exists() 

201 ): 

202 shutil.copy(orig_itp_file, Path(unique_dir_output_file).parent) 

203 fu.log( 

204 f"Copying {str(orig_itp_file)} to: {str(Path(unique_dir_output_file).parent)}", 

205 self.out_log, 

206 self.global_log, 

207 ) 

208 

209 # zip topology 

210 fu.log( 

211 "Compressing topology to: %s" % self.io_dict["out"]["output_top_zip_path"], 

212 self.out_log, 

213 self.global_log, 

214 ) 

215 fu.zip_top( 

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

217 top_file=str(Path(unique_dir_output_file)), 

218 out_log=self.out_log, 

219 remove_original_files=self.remove_tmp 

220 ) 

221 

222 self.tmp_files.extend([top_dir]) 

223 self.remove_tmp_files() 

224 

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

226 return self.return_code 

227 

228 

229def pmxgentop( 

230 input_top_zip_path: str, 

231 output_top_zip_path: str, 

232 properties: Optional[dict] = None, 

233 **kwargs, 

234) -> int: 

235 """Create the :class:`Pmxgentop <pmx.pmxgentop.Pmxgentop>` class and 

236 execute the :meth:`launch() <pmx.pmxgentop.Pmxgentop.launch> method.""" 

237 return Pmxgentop(**dict(locals())).launch() 

238 

239 

240pmxgentop.__doc__ = Pmxgentop.__doc__ 

241main = Pmxgentop.get_main(pmxgentop, "Wrapper class for the PMX gentop module") 

242 

243if __name__ == "__main__": 

244 main()