|
3 | 3 | ####################################
|
4 | 4 | ### Import dependencies
|
5 | 5 | from functions import *
|
6 |
| -import io |
7 |
| -from contextlib import redirect_stdout |
8 |
| - |
9 |
| -# Rich library for professional styling |
10 |
| -from rich.console import Console |
11 |
| -from rich.panel import Panel |
12 |
| -from rich.text import Text |
13 |
| -from rich.table import Table |
14 |
| -import rich.box |
15 | 6 |
|
16 | 7 | # Check if JAX-capable GPU is available, otherwise exit
|
17 | 8 | check_jax_gpu()
|
|
35 | 26 | ### load settings from JSON
|
36 | 27 | target_settings, advanced_settings, filters = load_json_settings(settings_path, filters_path, advanced_path)
|
37 | 28 |
|
38 |
| -#################################### |
39 |
| -################### Settings Monitor |
40 |
| -#################################### |
41 |
| -# Initialize console here to be used by all rich elements |
42 |
| -console = Console() |
43 |
| - |
44 |
| -# Create a Text object with all the settings, preserving the alignment |
45 |
| -settings_text = Text( |
46 |
| - f"design_path ---> {target_settings['design_path']}\n" |
47 |
| - f"binder_name ---> {target_settings['binder_name']}\n" |
48 |
| - f"starting_pdb file name ---> {os.path.basename(target_settings['starting_pdb'])}\n" |
49 |
| - f"chains ---> {target_settings['chains']}\n" |
50 |
| - f"target_hotspot_residues ---> {target_settings['target_hotspot_residues']}\n" |
51 |
| - f"lengths ---> {target_settings['lengths']}\n" |
52 |
| - f"number_of_final_designs ---> {target_settings['number_of_final_designs']}" |
53 |
| -) |
54 |
| - |
55 |
| -# Print the settings inside a styled panel |
56 |
| -console.print( |
57 |
| - Panel( |
58 |
| - settings_text, |
59 |
| - title="[bold white]Your Input Settings[/bold white]", |
60 |
| - border_style="blue", |
61 |
| - expand=False, |
62 |
| - padding=(1, 2) |
63 |
| - ) |
64 |
| -) |
65 |
| -console.print() # Add a newline for spacing |
66 |
| -#################################### |
67 |
| -#################################### |
68 |
| - |
69 | 29 | settings_file = os.path.basename(settings_path).split('.')[0]
|
70 | 30 | filters_file = os.path.basename(filters_path).split('.')[0]
|
71 | 31 | advanced_file = os.path.basename(advanced_path).split('.')[0]
|
|
96 | 56 | ####################################
|
97 | 57 | ####################################
|
98 | 58 | ####################################
|
99 |
| -### Print custom banner |
100 |
| -# Define styled content for the banner by appending lines to a single Text object |
101 |
| -banner_content = Text("BindCraft v1.5.2", style="bold bright_cyan", justify="center") |
102 |
| -banner_content.append("\nOne-shot Design of Functional Protein Binders", style="bold italic green") |
103 |
| -banner_content.append("\n\nIf you use BindCraft in your research, please cite:\n", style="default") |
104 |
| -banner_content.append( |
105 |
| - Text.assemble( |
106 |
| - Text( |
107 |
| - "Pacesa, M., Nickel, L., Schellhaas, C., Schmidt, J., Pyatova, E., Kissling, L., Barendse, P., Choudhury, J., Kapoor, S., Alcaraz-Serna, A., Cho, Y., Ghamary, K. H., Vinué, L., Yachnin, B. J., Wollacott, A. M., Buckley, S., Westphal, A. H., Lindhoud, S., Georgeon, S., . . . Correia, B. E.", "dim" |
108 |
| - ), |
109 |
| - ("BindCraft: one-shot design of functional protein binders. bioRxiv (Cold Spring Harbor Laboratory). https://doi.org/10.1101/2024.09.30.615802", "dim") |
110 |
| - ) |
111 |
| -) |
112 |
| - |
113 |
| - |
114 |
| -# Print content inside a styled panel |
115 |
| -console.print( |
116 |
| - Panel( |
117 |
| - banner_content, |
118 |
| - title="[bold white]Initialization[/bold white]", |
119 |
| - border_style="blue", |
120 |
| - expand=False, |
121 |
| - padding=(1, 2) |
122 |
| - ) |
123 |
| -) |
124 |
| -console.print() # Add a newline for spacing |
125 |
| - |
126 | 59 | ### initialise PyRosetta
|
127 |
| -pr.init(f'-ignore_unrecognized_res -ignore_zero_occupancy -mute all -holes:dalphaball {advanced_settings["dalphaball_path"]} -corrections::beta_nov16 true -relax:default_repeats 1', silent=True) |
128 |
| - |
129 |
| -# NEW: Display run configuration in a panel |
130 |
| -run_info_text = Text( |
131 |
| - f"Target: {settings_file}\n" |
132 |
| - f"Design Settings: {advanced_file}\n" |
133 |
| - f"Filter Settings: {filters_file}", |
134 |
| - justify="center" |
135 |
| -) |
136 |
| -console.print( |
137 |
| - Panel( |
138 |
| - run_info_text, |
139 |
| - title="[bold white]Run Configuration[/bold white]", |
140 |
| - border_style="green", |
141 |
| - ) |
142 |
| -) |
143 |
| -console.print() |
144 |
| - |
| 60 | +pr.init(f'-ignore_unrecognized_res -ignore_zero_occupancy -mute all -holes:dalphaball {advanced_settings["dalphaball_path"]} -corrections::beta_nov16 true -relax:default_repeats 1') |
| 61 | +print(f"Running binder design for target {settings_file}") |
| 62 | +print(f"Design settings used: {advanced_file}") |
| 63 | +print(f"Filtering designs based on {filters_file}") |
145 | 64 |
|
146 | 65 | ####################################
|
147 | 66 | # initialise counters
|
|
184 | 103 | trajectory_exists = any(os.path.exists(os.path.join(design_paths[trajectory_dir], design_name + ".pdb")) for trajectory_dir in trajectory_dirs)
|
185 | 104 |
|
186 | 105 | if not trajectory_exists:
|
187 |
| - # console.print(f"Starting trajectory: [bold cyan]{design_name}[/bold cyan]") |
188 |
| - log_buffer = io.StringIO() |
| 106 | + print("Starting trajectory: "+design_name) |
189 | 107 |
|
190 | 108 | ### Begin binder hallucination
|
191 |
| - # NEW: Use a status spinner and redirect verbose logs to a buffer |
192 |
| - with console.status(f"[bold green]Designing trajectory {design_name}...", spinner="dots") as status: |
193 |
| - with redirect_stdout(log_buffer): |
194 |
| - trajectory = binder_hallucination(design_name, target_settings["starting_pdb"], target_settings["chains"], |
195 |
| - target_settings["target_hotspot_residues"], length, seed, helicity_value, |
196 |
| - design_models, advanced_settings, design_paths, failure_csv) |
197 |
| - |
198 |
| - # Save the captured log to a file for debugging |
199 |
| - hallucination_log = log_buffer.getvalue() |
200 |
| - log_filepath = os.path.join(design_paths["Trajectory"], f"{design_name}.log") |
201 |
| - with open(log_filepath, "w") as log_file: |
202 |
| - log_file.write(hallucination_log) |
203 |
| - |
| 109 | + trajectory = binder_hallucination(design_name, target_settings["starting_pdb"], target_settings["chains"], |
| 110 | + target_settings["target_hotspot_residues"], length, seed, helicity_value, |
| 111 | + design_models, advanced_settings, design_paths, failure_csv) |
204 | 112 | trajectory_metrics = copy_dict(trajectory._tmp["best"]["aux"]["log"]) # contains plddt, ptm, i_ptm, pae, i_pae
|
205 | 113 | trajectory_pdb = os.path.join(design_paths["Trajectory"], design_name + ".pdb")
|
206 | 114 |
|
|
210 | 118 | # time trajectory
|
211 | 119 | trajectory_time = time.time() - trajectory_start_time
|
212 | 120 | trajectory_time_text = f"{'%d hours, %d minutes, %d seconds' % (int(trajectory_time // 3600), int((trajectory_time % 3600) // 60), int(trajectory_time % 60))}"
|
213 |
| - |
| 121 | + print("Starting trajectory took: "+trajectory_time_text) |
| 122 | + print("") |
| 123 | + |
214 | 124 | # Proceed if there is no trajectory termination signal
|
215 | 125 | if trajectory.aux["log"]["terminate"] == "":
|
216 | 126 | # Relax binder to calculate statistics
|
|
229 | 139 |
|
230 | 140 | # analyze interface scores for relaxed af2 trajectory
|
231 | 141 | trajectory_interface_scores, trajectory_interface_AA, trajectory_interface_residues = score_interface(trajectory_relaxed, binder_chain)
|
232 |
| - |
233 |
| - # NEW: Print a summary table for the trajectory |
234 |
| - summary_table = Table(title=f"Trajectory [bold cyan]{design_name}[/bold cyan] Initial Results", show_header=True, header_style="bold magenta", box=rich.box.ROUNDED) |
235 |
| - summary_table.add_column("Metric", style="cyan") |
236 |
| - summary_table.add_column("Value") |
237 |
| - summary_table.add_row("pLDDT", f"{trajectory_metrics.get('plddt', 'N/A'):.2f}") |
238 |
| - summary_table.add_row("Interface pTM", f"{trajectory_metrics.get('i_ptm', 'N/A'):.2f}") |
239 |
| - summary_table.add_row("Interface Clashes (Relaxed)", str(num_clashes_relaxed)) |
240 |
| - summary_table.add_row("Interface dG", f"{trajectory_interface_scores.get('interface_dG', 'N/A'):.2f}") |
241 |
| - summary_table.add_row("Design Time", trajectory_time_text) |
242 |
| - console.print(summary_table) |
243 |
| - console.print() |
244 | 142 |
|
245 | 143 | # starting binder sequence
|
246 | 144 | trajectory_sequence = trajectory.get_seq(get_best=True)[0]
|
|
262 | 160 | trajectory_alpha_interface, trajectory_beta_interface, trajectory_loops_interface, trajectory_alpha, trajectory_beta, trajectory_loops, trajectory_interface_AA, trajectory_target_rmsd,
|
263 | 161 | trajectory_time_text, traj_seq_notes, settings_file, filters_file, advanced_file]
|
264 | 162 | insert_data(trajectory_csv, trajectory_data)
|
| 163 | + |
| 164 | + if not trajectory_interface_residues: |
| 165 | + print("No interface residues found for "+str(design_name)+", skipping MPNN optimization") |
| 166 | + continue |
265 | 167 |
|
266 | 168 | if advanced_settings["enable_mpnn"]:
|
267 | 169 | # initialise MPNN counters
|
|
338 | 240 |
|
339 | 241 | # if AF2 filters are not passed then skip the scoring
|
340 | 242 | if not pass_af2_filters:
|
341 |
| - console.print(f"Skipping interface scoring for {mpnn_design_name} (failed base AF2 filters).") |
| 243 | + print(f"Base AF2 filters not passed for {mpnn_design_name}, skipping interface scoring") |
342 | 244 | mpnn_n += 1
|
343 | 245 | continue
|
344 | 246 |
|
|
474 | 376 | # run design data against filter thresholds
|
475 | 377 | filter_conditions = check_filters(mpnn_data, design_labels, filters)
|
476 | 378 | if filter_conditions == True:
|
477 |
| - console.print(f":heavy_check_mark: [bold green]SUCCESS: {mpnn_design_name} passed all filters.[/bold green]") |
| 379 | + print(mpnn_design_name+" passed all filters") |
478 | 380 | accepted_mpnn += 1
|
479 | 381 | accepted_designs += 1
|
480 | 382 |
|
|
501 | 403 | shutil.copy(source_plot, target_plot)
|
502 | 404 |
|
503 | 405 | else:
|
504 |
| - console.print(f":x: [bold yellow]REJECTED: {mpnn_design_name} failed filter checks.[/bold yellow]") |
| 406 | + print(f"Unmet filter conditions for {mpnn_design_name}") |
505 | 407 | failure_df = pd.read_csv(failure_csv)
|
506 | 408 | special_prefixes = ('Average_', '1_', '2_', '3_', '4_', '5_')
|
507 | 409 | incremented_columns = set()
|
|
527 | 429 | break
|
528 | 430 |
|
529 | 431 | if accepted_mpnn >= 1:
|
530 |
| - console.print(f"\n[bold green]Found {accepted_mpnn} valid MPNN designs for this trajectory.[/bold green]\n") |
| 432 | + print("Found "+str(accepted_mpnn)+" MPNN designs passing filters") |
| 433 | + print("") |
531 | 434 | else:
|
532 |
| - console.print("\n[yellow]No accepted MPNN designs found for this trajectory.[/yellow]\n") |
| 435 | + print("No accepted MPNN designs found for this trajectory.") |
| 436 | + print("") |
533 | 437 |
|
534 | 438 | else:
|
535 |
| - console.print('[yellow]Duplicate or invalid MPNN designs sampled, skipping current trajectory optimization.[/yellow]\n') |
| 439 | + print('Duplicate MPNN designs sampled with different trajectory, skipping current trajectory optimisation') |
| 440 | + print("") |
536 | 441 |
|
537 | 442 | # save space by removing unrelaxed design trajectory PDB
|
538 | 443 | if advanced_settings["remove_unrelaxed_trajectory"]:
|
|
541 | 446 | # measure time it took to generate designs for one trajectory
|
542 | 447 | design_time = time.time() - design_start_time
|
543 | 448 | design_time_text = f"{'%d hours, %d minutes, %d seconds' % (int(design_time // 3600), int((design_time % 3600) // 60), int(design_time % 60))}"
|
544 |
| - console.print(f"Design and validation of trajectory {design_name} took: [bold]{design_time_text}[/bold]") |
545 |
| - console.print("-" * 50) |
546 |
| - |
| 449 | + print("Design and validation of trajectory "+design_name+" took: "+design_time_text) |
547 | 450 |
|
548 | 451 | # analyse the rejection rate of trajectories to see if we need to readjust the design weights
|
549 | 452 | if trajectory_n >= advanced_settings["start_monitoring"] and advanced_settings["enable_rejection_check"]:
|
550 | 453 | acceptance = accepted_designs / trajectory_n
|
551 | 454 | if not acceptance >= advanced_settings["acceptance_rate"]:
|
552 |
| - console.print(f"[bold red]ACCEPTANCE RATE WARNING: The ratio of successful designs ({acceptance:.2%}) is lower than the target ({advanced_settings['acceptance_rate']:.2%}).[/bold red]") |
553 |
| - console.print("[bold red]Consider changing your design settings. Stopping script execution.[/bold red]") |
| 455 | + print("The ratio of successful designs is lower than defined acceptance rate! Consider changing your design settings!") |
| 456 | + print("Script execution stopping...") |
554 | 457 | break
|
555 | 458 |
|
556 | 459 | # increase trajectory number
|
|
560 | 463 | ### Script finished
|
561 | 464 | elapsed_time = time.time() - script_start_time
|
562 | 465 | elapsed_text = f"{'%d hours, %d minutes, %d seconds' % (int(elapsed_time // 3600), int((elapsed_time % 3600) // 60), int(elapsed_time % 60))}"
|
563 |
| -console.print( |
564 |
| - Panel( |
565 |
| - f"Script finished after generating [bold]{trajectory_n-1}[/bold] trajectories.\nTotal execution time: [bold green]{elapsed_text}[/bold green]", |
566 |
| - title="[bold white]Run Complete[/bold white]", |
567 |
| - border_style="green" |
568 |
| - ) |
569 |
| -) |
| 466 | +print("Finished all designs. Script execution for "+str(trajectory_n)+" trajectories took: "+elapsed_text) |
0 commit comments