Coverage for biobb_analysis/ambertools/cpptraj_cluster.py: 95%

92 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-07 12:30 +0000

1#!/usr/bin/env python3 

2 

3"""Module containing the Cpptraj Cluster class and the command line interface.""" 

4 

5from typing import Optional 

6from pathlib import PurePath 

7from biobb_common.generic.biobb_object import BiobbObject 

8from biobb_common.tools.file_utils import launchlogger 

9from biobb_analysis.ambertools.common import get_default_value, check_top_path, check_traj_path, check_out_path, get_binary_path, get_in_parameters, get_negative_mask, setup_structure, get_cluster_algorithm, get_cluster_metric, get_traj_format 

10 

11 

12class CpptrajCluster(BiobbObject): 

13 """ 

14 | biobb_analysis CpptrajCluster 

15 | Wrapper of the Ambertools Cpptraj module for clustering the frames of a given cpptraj compatible trajectory. 

16 | Cpptraj (the successor to ptraj) is the main program in Ambertools for processing coordinate trajectories and data files. The parameter names and defaults are the same as the ones in the official `Cpptraj manual <https://raw.githubusercontent.com/Amber-MD/cpptraj/master/doc/CpptrajManual.pdf>`_. 

17 

18 Args: 

19 input_top_path (str): Path to the input structure or topology file. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/ambertools/cpptraj.parm.top>`_. Accepted formats: top (edam:format_3881), pdb (edam:format_1476), prmtop (edam:format_3881), parmtop (edam:format_3881), zip (edam:format_3987). 

20 input_traj_path (str): Path to the input trajectory to be processed. File type: input. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/data/ambertools/cpptraj.traj.dcd>`_. Accepted formats: mdcrd (edam:format_3878), crd (edam:format_3878), cdf (edam:format_3650), netcdf (edam:format_3650), nc (edam:format_3650), restart (edam:format_3886), ncrestart (edam:format_3886), restartnc (edam:format_3886), dcd (edam:format_3878), charmm (edam:format_3887), cor (edam:format_2033), pdb (edam:format_1476), mol2 (edam:format_3816), trr (edam:format_3910), gro (edam:format_2033), binpos (edam:format_3885), xtc (edam:format_3875), cif (edam:format_1477), arc (edam:format_2333), sqm (edam:format_2033), sdf (edam:format_3814), conflib (edam:format_2033). 

21 output_cpptraj_path (str): Path to the output cluster number vs time analysis. File type: output. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/reference/ambertools/ref_cpptraj.cluster.dat>`_. Accepted formats: dat (edam:format_1637), agr (edam:format_2033), xmgr (edam:format_2033), gnu (edam:format_2033). 

22 output_summary_path (str) (Optional): Path to the output summary of the generated clusters. File type: output. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/reference/ambertools/ref_cpptraj.cluster.summary.dat>`_. Accepted formats: dat (edam:format_1637), agr (edam:format_2033), xmgr (edam:format_2033), gnu (edam:format_2033). 

23 output_info_path (str) (Optional): Path to the output detailed information of the generated clusters. File type: output. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/reference/ambertools/ref_cpptraj.cluster.info.dat>`_. Accepted formats: dat (edam:format_1637), agr (edam:format_2033), xmgr (edam:format_2033), gnu (edam:format_2033). 

24 output_traj_path (str) (Optional): Path to the output trajectory containing the representative frame of every cluster. File type: output. `Sample file <https://github.com/bioexcel/biobb_analysis/raw/master/biobb_analysis/test/reference/ambertools/ref_cpptraj.cluster.netcdf>`_. Accepted formats: mdcrd (edam:format_3878), crd (edam:format_3878), cdf (edam:format_3650), netcdf (edam:format_3650), nc (edam:format_3650), restart (edam:format_3886), ncrestart (edam:format_3886), restartnc (edam:format_3886), dcd (edam:format_3878), charmm (edam:format_3887), cor (edam:format_2033), pdb (edam:format_1476), mol2 (edam:format_3816), trr (edam:format_3910), gro (edam:format_2033), binpos (edam:format_3885), xtc (edam:format_3875), cif (edam:format_1477), arc (edam:format_2333), sqm (edam:format_2033), sdf (edam:format_3814), conflib (edam:format_2033). 

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

26 * **start** (*int*) - (1) [1~100000|1] Starting frame for slicing 

27 * **end** (*int*) - (-1) [-1~100000|1] Ending frame for slicing 

28 * **steps** (*int*) - (1) [1~100000|1] Step for slicing 

29 * **mask** (*str*) - ("all-atoms") Mask definition. Values: c-alpha (All c-alpha atoms; protein only), backbone (Backbone atoms), all-atoms (All system atoms), heavy-atoms (System heavy atoms; not hydrogen), side-chain (All not backbone atoms), solute (All system atoms except solvent atoms), ions (All ion molecules), solvent (All solvent atoms), AnyAmberFromatMask (Amber atom selection syntax like `@*`). 

30 * **algorithm** (*str*) - ("hieragglo") Clustering algorithm. Values: hieragglo (Hierarchical agglomerative -bottom up- clustering), dbscan (Density-based spatial clustering of applications with noise), kmeans (K-means clustering), dpeaks (Clustering by fast search and find of density peaks; Cpptraj leaves an extra temp.dat file in the working directory). 

31 * **clusters** (*int*) - (10) [1~1000|1] Target number of clusters. Only used by the hieragglo and kmeans algorithms. 

32 * **epsilon** (*float*) - (-1.0) [-1~100|0.1] Minimum distance between clusters. Mandatory for the dbscan and dpeaks algorithms; for hieragglo it is an additional stop condition, only used when greater than 0. 

33 * **minpoints** (*int*) - (4) [1~1000|1] Minimum number of points required to form a cluster. Only used by the dbscan algorithm. 

34 * **linkage** (*str*) - ("linkage") Distance between clusters. Only used by the hieragglo algorithm. Values: linkage (Shortest distance between members of the two clusters), averagelinkage (Average distance between members of the two clusters), complete (Longest distance between members of the two clusters). 

35 * **metric** (*str*) - ("rms") Distance metric between frames. Values: rms (Coordinate root mean square deviation), srmsd (Symmetry-corrected coordinate root mean square deviation), dme (Distance root mean square deviation), qrmsd (Quaternion root mean square deviation). 

36 * **metric_mask** (*str*) - ("all-atoms") Mask used for the distance metric calculation. Unlike **mask**, the atoms outside this selection are kept in the clustered coordinates, they are only left out of the frame to frame distance calculation. Values: c-alpha (All c-alpha atoms; protein only), backbone (Backbone atoms), all-atoms (All system atoms), heavy-atoms (System heavy atoms; not hydrogen), side-chain (All not backbone atoms), solute (All system atoms except solvent atoms), ions (All ion molecules), solvent (All solvent atoms), AnyAmberFromatMask (Amber atom selection syntax like `@*`). 

37 * **mass** (*bool*) - (False) Mass-weight the distance metric. 

38 * **nofit** (*bool*) - (False) Do not best-fit the frames before calculating the distance metric. 

39 * **sieve** (*int*) - (0) [0~100000|1] Cluster only every nth frame and add the remaining frames back in afterwards. 0 disables sieving. 

40 * **binary_path** (*str*) - ("cpptraj") Path to the cpptraj executable binary. 

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

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

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

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

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

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

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

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

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

50 

51 Examples: 

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

53 

54 from biobb_analysis.ambertools.cpptraj_cluster import cpptraj_cluster 

55 prop = { 

56 'start': 1, 

57 'end': -1, 

58 'steps': 1, 

59 'mask': 'c-alpha', 

60 'algorithm': 'hieragglo', 

61 'clusters': 5 

62 } 

63 cpptraj_cluster(input_top_path='/path/to/myTopology.top', 

64 input_traj_path='/path/to/myTrajectory.dcd', 

65 output_cpptraj_path='/path/to/newAnalysis.dat', 

66 output_summary_path='/path/to/newSummary.dat', 

67 output_info_path='/path/to/newInfo.dat', 

68 output_traj_path='/path/to/newTrajectory.netcdf', 

69 properties=prop) 

70 

71 Info: 

72 * wrapped_software: 

73 * name: Ambertools Cpptraj 

74 * version: >=22.5 

75 * license: GNU 

76 * ontology: 

77 * name: EDAM 

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

79 

80 """ 

81 

82 def __init__(self, input_top_path, input_traj_path, output_cpptraj_path, 

83 output_summary_path=None, output_info_path=None, output_traj_path=None, 

84 properties=None, **kwargs) -> None: 

85 properties = properties or {} 

86 

87 # Call parent class constructor 

88 super().__init__(properties) 

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

90 

91 # Input/Output files 

92 self.io_dict = { 

93 "in": {"input_top_path": input_top_path, "input_traj_path": input_traj_path}, 

94 "out": {"output_cpptraj_path": output_cpptraj_path, "output_summary_path": output_summary_path, 

95 "output_info_path": output_info_path, "output_traj_path": output_traj_path} 

96 } 

97 

98 # Properties specific for BB 

99 self.instructions_file = get_default_value('instructions_file') 

100 self.start = properties.get('start', 1) 

101 self.end = properties.get('end', -1) 

102 self.steps = properties.get('steps', 1) 

103 self.mask = properties.get('mask', 'all-atoms') 

104 self.algorithm = properties.get('algorithm', 'hieragglo') 

105 self.clusters = properties.get('clusters', 10) 

106 self.epsilon = properties.get('epsilon', -1.0) 

107 self.minpoints = properties.get('minpoints', 4) 

108 self.linkage = properties.get('linkage', 'linkage') 

109 self.metric = properties.get('metric', 'rms') 

110 self.metric_mask = properties.get('metric_mask', 'all-atoms') 

111 self.mass = properties.get('mass', False) 

112 self.nofit = properties.get('nofit', False) 

113 self.sieve = properties.get('sieve', 0) 

114 self.properties = properties 

115 self.binary_path = get_binary_path(properties, 'binary_path') 

116 

117 # Check the properties 

118 self.check_init(properties) 

119 

120 def check_data_params(self, out_log, err_log): 

121 """ Checks all the input/output paths and parameters """ 

122 self.io_dict["in"]["input_top_path"], self.input_top_path_orig = check_top_path(self.io_dict["in"]["input_top_path"], out_log, self.__class__.__name__) 

123 self.io_dict["in"]["input_traj_path"] = check_traj_path(self.io_dict["in"]["input_traj_path"], out_log, self.__class__.__name__) 

124 self.io_dict["out"]["output_cpptraj_path"] = check_out_path(self.io_dict["out"]["output_cpptraj_path"], out_log, self.__class__.__name__) 

125 for out_file in ("output_summary_path", "output_info_path", "output_traj_path"): 

126 if self.io_dict["out"][out_file]: 

127 self.io_dict["out"][out_file] = check_out_path(self.io_dict["out"][out_file], out_log, self.__class__.__name__) 

128 self.in_parameters = {'start': self.start, 'end': self.end, 'step': self.steps, 'mask': self.mask} 

129 

130 def create_instructions_file(self, container_io_dict, out_log, err_log): 

131 """Creates an input file using the properties file settings""" 

132 instructions_list = [] 

133 # different path if container execution or not 

134 if self.container_path: 

135 self.instructions_file = str(PurePath(self.stage_io_dict['unique_dir']).joinpath("cpptraj.in")) 

136 self.instructions_file_path = str(PurePath(self.container_volume_path).joinpath("cpptraj.in")) 

137 else: 

138 self.instructions_file = self.create_tmp_file(self.instructions_file) 

139 self.instructions_file_path = self.instructions_file 

140 

141 # parm 

142 instructions_list.append('parm ' + container_io_dict["in"]["input_top_path"]) 

143 

144 # trajin 

145 in_params = get_in_parameters(self.in_parameters, out_log) 

146 instructions_list.append('trajin ' + container_io_dict["in"]["input_traj_path"] + ' ' + in_params) 

147 

148 # Set up 

149 instructions_list += setup_structure(self) 

150 

151 # mask 

152 mask = self.in_parameters.get('mask', '') 

153 if mask: 

154 strip_mask = get_negative_mask(mask, out_log) 

155 instructions_list.append('strip ' + strip_mask) 

156 

157 # the cluster analysis works on a COORDS data set, so the processed frames must be saved first 

158 cluster_set = get_default_value('cluster_set') 

159 instructions_list.append('createcrd ' + cluster_set) 

160 instructions_list.append('run') 

161 

162 # cluster 

163 cluster = ['cluster', 'crdset', cluster_set] 

164 cluster.append(get_cluster_algorithm(self.algorithm, self.clusters, self.epsilon, self.minpoints, self.linkage, out_log)) 

165 cluster.append(get_cluster_metric(self.metric, self.mass, self.nofit, self.metric_mask, out_log)) 

166 if self.sieve: 

167 cluster += ['sieve', str(self.sieve)] 

168 cluster += ['out', container_io_dict["out"]["output_cpptraj_path"]] 

169 if container_io_dict["out"].get("output_summary_path"): 

170 cluster += ['summary', container_io_dict["out"]["output_summary_path"]] 

171 if container_io_dict["out"].get("output_info_path"): 

172 cluster += ['info', container_io_dict["out"]["output_info_path"]] 

173 if container_io_dict["out"].get("output_traj_path"): 

174 traj_path = container_io_dict["out"]["output_traj_path"] 

175 cluster += ['singlerepout', traj_path, 'singlerepfmt', get_traj_format(traj_path, out_log)] 

176 instructions_list.append(' '.join(cluster)) 

177 

178 # create .in file 

179 with open(self.instructions_file, 'w') as mdp: 

180 for line in instructions_list: 

181 mdp.write(line.strip() + '\n') 

182 

183 return self.instructions_file_path 

184 

185 @launchlogger 

186 def launch(self) -> int: 

187 """Execute the :class:`CpptrajCluster <ambertools.cpptraj_cluster.CpptrajCluster>` object.""" 

188 

189 # check input/output paths and parameters 

190 self.check_data_params(self.out_log, self.err_log) 

191 

192 # Setup Biobb 

193 if self.check_restart(): 

194 return 0 

195 self.stage_files() 

196 

197 # create instructions file 

198 self.create_instructions_file(self.stage_io_dict, self.out_log, self.err_log) 

199 

200 # create cmd and launch execution 

201 self.cmd = [self.binary_path, '-i', self.instructions_file_path] 

202 

203 # Run Biobb block 

204 self.run_biobb() 

205 

206 # Copy files to host 

207 self.copy_to_host() 

208 

209 # remove temporary folder(s) 

210 self.remove_tmp_files() 

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

212 

213 return self.return_code 

214 

215 

216def cpptraj_cluster(input_top_path: str, input_traj_path: str, output_cpptraj_path: str, output_summary_path: Optional[str] = None, output_info_path: Optional[str] = None, output_traj_path: Optional[str] = None, properties: Optional[dict] = None, **kwargs) -> int: 

217 """Create the :class:`CpptrajCluster <ambertools.cpptraj_cluster.CpptrajCluster>` class and 

218 execute the :meth:`launch() <ambertools.cpptraj_cluster.CpptrajCluster.launch>` method.""" 

219 return CpptrajCluster(**dict(locals())).launch() 

220 

221 

222cpptraj_cluster.__doc__ = CpptrajCluster.__doc__ 

223main = CpptrajCluster.get_main(cpptraj_cluster, "Clusters the frames of a given cpptraj compatible trajectory.") 

224 

225if __name__ == '__main__': 

226 main()