Coverage for biobb_pmx / pmxbiobb / pmxmutate.py: 84%

63 statements  

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

1#!/usr/bin/env python3 

2 

3"""Module containing the PMX mutate 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 

15from biobb_pmx.pmxbiobb.common import MUTATION_DICT, create_mutations_file 

16 

17 

18class Pmxmutate(BiobbObject): 

19 """ 

20 | biobb_pmx Pmxmutate 

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

22 | Mutate residues in a protein structure. 

23 

24 Args: 

25 input_structure_path (str): Path to the input structure file. File type: input. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/data/pmx/frame99.pdb>`_. Accepted formats: pdb (edam:format_1476), gro (edam:format_2033). 

26 output_structure_path (str): Path to the output structure file. File type: output. `Sample file <https://github.com/bioexcel/biobb_pmx/raw/master/biobb_pmx/test/reference/pmx/ref_output_structure.pdb>`_. Accepted formats: pdb (edam:format_1476), gro (edam:format_2033). 

27 input_b_structure_path (str) (Optional): Path to the mutated input structure file. File type: input. Accepted formats: pdb (edam:format_1476), gro (edam:format_2033). 

28 properties (dic): 

29 * **mutation_list** (*str*) - ("2Ala") Mutation list in the format "Chain:Resnum MUT_AA_Code" or "Chain:Resnum MUT_NA_Code" (no spaces between the elements) separated by commas. If no chain is provided as chain code all the chains in the pdb file will be mutated. ie: "A:15CYS". Possible MUT_AA_Code: 'ALA', 'ARG', 'ASN', 'ASP', 'ASPH', 'ASPP', 'ASH', 'CYS', 'CYS2', 'CYN', 'CYX', 'CYM', 'CYSH', 'GLU', 'GLUH', 'GLUP', 'GLH', 'GLN', 'GLY', 'HIS', 'HIE', 'HISE', 'HSE', 'HIP', 'HSP', 'HISH', 'HID', 'HSD', 'ILE', 'LEU', 'LYS', 'LYSH', 'LYP', 'LYN', 'LSN', 'MET', 'PHE', 'PRO', 'SER', 'SP1', 'SP2', 'THR', 'TRP', 'TYR', 'VAL'. Possible MUT_NA_Codes: 'A', 'T', 'C', 'G', 'U'. 

30 * **force_field** (*str*) - ("amber99sb-star-ildn-mut") Forcefield to use. 

31 * **resinfo** (*bool*) - (False) Show the list of 3-letter -> 1-letter residues. 

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

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

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

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

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

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

38 * **container_image** (*str*) - (None) Container Image identifier. 

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

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

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

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

43 

44 Examples: 

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

46 

47 from biobb_pmx.pmxbiobb.pmxmutate import pmxmutate 

48 prop = { 

49 'mutation_list': '2Ala, 3Val', 

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

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

52 } 

53 pmxmutate(input_structure_path='/path/to/myStructure.pdb', 

54 output_structure_path='/path/to/newStructure.pdb', 

55 input_b_structure_path='/path/to/myStructureB.pdb' 

56 properties=prop) 

57 

58 Info: 

59 * wrapped_software: 

60 * name: PMX mutate 

61 * version: >=1.0.1 

62 * license: GNU 

63 * ontology: 

64 * name: EDAM 

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

66 

67 """ 

68 

69 def __init__( 

70 self, 

71 input_structure_path: str, 

72 output_structure_path: str, 

73 input_b_structure_path: Optional[str] = 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": { 

86 "input_structure_path": input_structure_path, 

87 "input_b_structure_path": input_b_structure_path, 

88 }, 

89 "out": {"output_structure_path": output_structure_path}, 

90 } 

91 

92 # Properties specific for BB 

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

94 self.resinfo = properties.get("resinfo", False) 

95 self.mutation_list = properties.get("mutation_list", "2Ala") 

96 self.input_mutations_file = properties.get("mutations_file") 

97 

98 # Properties common in all PMX BB 

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

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

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

102 self.gmx_lib = str( 

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

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

105 ) 

106 ) 

107 if properties.get("container_path"): 

108 self.gmx_lib = str( 

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

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

111 ) 

112 ) 

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

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:`Pmxmutate <pmx.pmxmutate.Pmxmutate>` pmx.pmxmutate.Pmxmutate object.""" 

122 

123 # Setup Biobb 

124 if self.check_restart(): 

125 return 0 

126 self.stage_files() 

127 

128 # Check if executable exists 

129 if not self.container_path: 

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

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

132 raise FileNotFoundError( 

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

134 % self.binary_path 

135 ) 

136 

137 # Generate mutations file 

138 

139 mutations_dir = fu.create_unique_dir() 

140 self.input_mutations_file = create_mutations_file( 

141 input_mutations_path=str(Path(mutations_dir).joinpath("mutations.txt")), 

142 mutation_list=self.mutation_list, 

143 mutation_dict=MUTATION_DICT, 

144 ) 

145 

146 # Copy extra files to container: mutations file 

147 if self.container_path: 

148 fu.log("Container execution enabled", self.out_log) 

149 

150 shutil.copy2( 

151 self.input_mutations_file, self.stage_io_dict.get("unique_dir", "") 

152 ) 

153 self.input_mutations_file = str( 

154 Path(self.container_volume_path).joinpath( 

155 Path(self.input_mutations_file).name 

156 ) 

157 ) 

158 

159 self.cmd = [ 

160 self.binary_path, 

161 "mutate", 

162 "-f", 

163 self.stage_io_dict["in"]["input_structure_path"], 

164 "-o", 

165 self.stage_io_dict["out"]["output_structure_path"], 

166 "-ff", 

167 self.force_field, 

168 "--script", 

169 self.input_mutations_file, 

170 ] 

171 

172 if self.stage_io_dict["in"].get("input_b_structure_path"): 

173 self.cmd.append("-fB") 

174 self.cmd.append(self.stage_io_dict["in"]["input_b_structure_path"]) 

175 if self.resinfo: 

176 self.cmd.append("-resinfo") 

177 

178 if self.gmx_lib: 

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

180 

181 # Run Biobb block 

182 self.run_biobb() 

183 

184 # Copy files to host 

185 self.copy_to_host() 

186 

187 self.tmp_files.append(mutations_dir) 

188 self.remove_tmp_files() 

189 

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

191 return self.return_code 

192 

193 

194def pmxmutate( 

195 input_structure_path: str, 

196 output_structure_path: str, 

197 input_b_structure_path: Optional[str] = None, 

198 properties: Optional[dict] = None, 

199 **kwargs, 

200) -> int: 

201 """Create the :class:`Pmxmutate <pmx.pmxmutate.Pmxmutate>` class and 

202 execute the :meth:`launch() <pmx.pmxmutate.Pmxmutate.launch> method.""" 

203 return Pmxmutate(**dict(locals())).launch() 

204 

205 

206pmxmutate.__doc__ = Pmxmutate.__doc__ 

207main = Pmxmutate.get_main(pmxmutate, "Run PMX mutate module") 

208 

209if __name__ == "__main__": 

210 main()