Coverage for biobb_haddock/haddock/common.py: 0%
148 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-13 14:55 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-13 14:55 +0000
1"""Common functions for package biobb_haddock.haddock"""
3import shutil
4import logging
5import os
6import jsonpickle
7from pathlib import Path
8from typing import Any, Optional
9import biobb_common.tools.file_utils as fu
10from biobb_common.generic.biobb_object import BiobbObject
11from biobb_common.tools.file_utils import launchlogger
12from .haddock3_config import load, save
14haddock_2_wf = {
15 'ambig_fname': 'ambig_restraints_table_path',
16 'unambig_fname': 'unambig_restraints_table_path',
17 'hbond_fname': 'hb_restraints_table_path',
18}
21class HaddockStepBase(BiobbObject):
22 """Base class for HADDOCK3 step modules with shared launch functionality."""
24 def copy_step_output(
25 self, filter_funct: callable,
26 output_zip_path: str, sele_top: bool = False) -> None:
27 """Copy the output files from the run directory to the output zip path.
29 Args:
30 obj: The object containing the output paths.
31 run_dir (str): The directory where the output files are located.
32 filter_funct (callable): A function that accepts a Path and returns True for the files to be copied.
33 output_zip_path (str): The path where the output zip file will be created."""
34 # Find the directories with the haddock step name
35 haddock_output_list = [
36 str(path)
37 for path in Path(self.run_dir).iterdir()
38 if path.is_dir() and str(path).endswith(self.haddock_step_name)
39 ]
40 # Make the one with the highest step number the first one
41 haddock_output_list.sort(reverse=True)
42 # Select files with filter_funct
43 output_file_list = [
44 str(path)
45 for path in Path(haddock_output_list[0]).iterdir()
46 if path.is_file() and filter_funct(path)
47 ]
48 if sele_top:
49 with open(haddock_output_list[0]+'/io.json') as json_file:
50 content = jsonpickle.decode(json_file.read())
51 output = content["output"]
52 for file in output:
53 rel_path = str(file.rel_path).split('/')
54 output_file_list.extend(list(Path(self.run_dir+'/'+rel_path[-2]).glob(rel_path[-1]+'*')))
55 if len(output_file_list) == 0:
56 fu.log("No output files found matching the criteria.", self.out_log, self.global_log)
57 else:
58 fu.zip_list(output_zip_path, output_file_list, self.out_log)
60 @launchlogger
61 def launch(self) -> int:
62 """Execute the HADDOCK step with common workflow."""
63 # Setup Biobb
64 if self.check_restart():
65 return 0
66 self.stage_files()
68 if self.stage_io_dict["in"]["input_haddock_wf_data"][-4:] == ".zip":
69 # Unzip workflow data to workflow_data_out
70 new_input = fu.create_unique_dir(self.stage_io_dict["unique_dir"], '_input_unzipped')
71 fu.unzip_list(self.stage_io_dict["in"]["input_haddock_wf_data"], new_input) # , self.out_log)
72 self.stage_io_dict["in"]["input_haddock_wf_data"] = new_input
74 self.run_dir = self.stage_io_dict["out"]["output_haddock_wf_data"]
75 if self.stage_io_dict["in"]["input_haddock_wf_data"] != self.run_dir:
76 # Different I/O folder
77 shutil.copytree(self.stage_io_dict["in"]["input_haddock_wf_data"],
78 self.run_dir, dirs_exist_ok=True)
79 else:
80 # Same I/O folder
81 os.rename(self.stage_io_dict["in"]["input_haddock_wf_data"], self.run_dir)
82 # Check if there are more than 9 numbered folders and rename them to add leading zeros
83 input_wf = self.io_dict["in"]["input_haddock_wf_data"]
84 numbered_dirs = []
85 for item in os.listdir(input_wf):
86 item_path = os.path.join(input_wf, item)
87 if os.path.isdir(item_path) and item[0].isdigit():
88 numbered_dirs.append(item)
90 if len(numbered_dirs) in [10, 100, 1000] and not self.disable_sandbox:
91 for dirname in numbered_dirs:
92 os.rename(os.path.join(input_wf, dirname),
93 os.path.join(input_wf, f"0{dirname}"))
95 workflow_dict = {"haddock_step_name": self.haddock_step_name}
96 workflow_dict.update(self.global_cfg)
98 if hasattr(self, '_handle_config_arguments'):
99 self._handle_config_arguments()
101 # Create workflow configuration
102 self.output_cfg_path = create_cfg(
103 output_cfg_path=self.create_tmp_file('_haddock.cfg'),
104 workflow_dict=workflow_dict,
105 input_cfg_path=self.stage_io_dict["in"].get("haddock_config_path"),
106 properties_cfg_dict=self.cfg,
107 out_log=self.out_log,
108 global_log=self.global_log,
109 )
111 if self.container_path:
112 fu.log("Container execution enabled", self.out_log)
113 move_to_container_path(self, self.run_dir)
115 self.cmd = [self.binary_path, self.output_cfg_path]
117 if self.haddock_step_name != "haddock3_run":
118 self.cmd.extend(["--extend-run", os.path.abspath(self.run_dir)])
120 # Run Biobb block
121 with fu.change_dir(self.run_dir):
122 self.run_biobb()
124 # Copy files to host
125 if hasattr(self, '_handle_step_output'):
126 self._handle_step_output()
127 if self.io_dict["out"]["output_haddock_wf_data"][-4:] == ".zip":
128 zip_wf_output(self)
129 else:
130 self.copy_to_host()
132 # Remove temporal files
133 self.remove_tmp_files()
135 return self.return_code
138def create_cfg(
139 output_cfg_path: str,
140 workflow_dict: dict[str, Any],
141 input_cfg_path: Optional[str] = None,
142 properties_cfg_dict: Optional[dict[str, str]] = None,
143 out_log: Optional[logging.Logger] = None,
144 global_log: Optional[logging.Logger] = None,
145) -> str:
146 """Creates an CFG file using the following hierarchy: properties_cfg_dict > input_cfg_path > preset_dict"""
147 cfg_dict: dict[str, Any] = {}
149 # Handle input configuration if it exists
150 if input_cfg_path:
151 input_cfg = load(input_cfg_path)
152 print(f"Input CFG: {input_cfg}")
153 cfg_dict = input_cfg.copy() # Start with entire loaded config as base
155 # Apply single step configuration if specified
156 haddock_step_name: str = workflow_dict["haddock_step_name"]
157 if not haddock_step_name.startswith("haddock3_"):
158 # Get preset properties for this step if any
159 step_preset = cfg_preset(haddock_step_name)
161 # Create or update the step configuration
162 if not cfg_dict:
163 # No input config, create new structure with single step
164 target_key = haddock_step_name
165 cfg_dict = {target_key: step_preset or {}}
166 else:
167 # Update the specific step in the existing config
168 target_key = f"{haddock_step_name}.1"
169 if target_key not in cfg_dict:
170 cfg_dict[target_key] = {}
171 # Merge preset values while preserving existing values
172 if step_preset:
173 for k, v in step_preset.items():
174 if k not in cfg_dict[target_key]: # Only add if not already defined
175 cfg_dict[target_key][k] = v
177 # Apply custom properties to the step
178 if properties_cfg_dict:
179 for k, v in properties_cfg_dict.items():
180 fu.log(f"CFG from properties: {k} = {v}", out_log, global_log)
181 cfg_dict[target_key][k] = v
182 # Multiple steps: haddock3_run and haddock3_extend
183 else:
184 if properties_cfg_dict:
185 for key, value in properties_cfg_dict.items():
186 if isinstance(value, dict):
187 # If the value is a dictionary, update the corresponding section in cfg_dict
188 if key not in cfg_dict:
189 cfg_dict[key] = {}
190 for sub_key, sub_value in value.items():
191 fu.log(f"CFG: {key}.{sub_key} = {sub_value}", out_log, global_log)
192 cfg_dict[key][sub_key] = sub_value
193 else:
194 # If the value is not a dictionary, treat it as a top-level property
195 fu.log(f"CFG: {key} = {value}", out_log, global_log)
196 cfg_dict[key] = value
197 # Add workflow_dict properties to cfg_dict
198 for key, value in cfg_dict.items():
199 if isinstance(value, dict):
200 for sub_key, sub_value in value.items():
201 mapped_key = haddock_2_wf.get(sub_key)
202 if mapped_key and mapped_key in workflow_dict:
203 sub_value = workflow_dict[mapped_key]
204 cfg_dict[key][sub_key] = sub_value
205 if haddock_step_name == "haddock3_run" and "run_dir" not in cfg_dict:
206 cfg_dict["run_dir"] = "run"
208 # Add molecules and run_dir if provided
209 for key, value in workflow_dict.items():
210 if key == 'haddock_step_name' or key in haddock_2_wf.values():
211 continue
212 fu.log(f"CFG: {key} = {value}", out_log, global_log)
213 cfg_dict[key] = value
215 # Use haddock save
216 save(cfg_dict, output_cfg_path)
218 return output_cfg_path
221def cfg_preset(haddock_step_name: str) -> dict[str, Any]:
222 cfg_dict: dict[str, Any] = {}
223 # cfg_dict["debug"] = True
225 if haddock_step_name == "topoaa":
226 cfg_dict["autohis"] = True
227 cfg_dict["delenph"] = True
228 cfg_dict["log_level"] = "quiet"
229 cfg_dict["iniseed"] = 917
230 cfg_dict["ligand_param_fname"] = ""
231 cfg_dict["ligand_top_fname"] = ""
232 cfg_dict["limit"] = True
233 cfg_dict["tolerance"] = 0
235 elif haddock_step_name == "rigidbody":
236 cfg_dict["sampling"] = 20
237 cfg_dict["tolerance"] = 20
239 elif haddock_step_name == "seletop":
240 cfg_dict["select"] = 5
242 elif haddock_step_name == "flexref":
243 cfg_dict["tolerance"] = 20
245 elif haddock_step_name == "emref":
246 cfg_dict["tolerance"] = 20
248 return cfg_dict
251def move_to_container_path(obj, run_dir=None):
252 """Move configuration and run directory to container path."""
253 shutil.copy2(obj.output_cfg_path, obj.stage_io_dict.get("unique_dir", ""))
254 obj.output_cfg_path = str(
255 Path(obj.container_volume_path).joinpath(
256 Path(obj.output_cfg_path).name
257 )
258 )
259 if run_dir:
260 shutil.copytree(
261 run_dir,
262 str(
263 Path(obj.stage_io_dict.get("unique_dir", "")).joinpath(
264 Path(run_dir).name
265 )
266 ),
267 )
268 run_dir = str(Path(obj.stage_io_dict.get("unique_dir", "")).joinpath(Path(run_dir).name))
271def zip_wf_output(obj):
272 """Zip all the files in the run directory and save it to the output path."""
273 dest_path = str(Path(obj.io_dict["out"]["output_haddock_wf_data"]).with_suffix(''))
274 fu.log(f"Zipping {obj.run_dir} to {dest_path} ", obj.out_log, obj.global_log)
275 shutil.make_archive(dest_path, "zip", obj.run_dir)