Skip to content

Commit e66eaa8

Browse files
committed
BindCraft setup via uv
1 parent 342820a commit e66eaa8

File tree

3 files changed

+199
-227
lines changed

3 files changed

+199
-227
lines changed

bindcraft.py

Lines changed: 28 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@
33
####################################
44
### Import dependencies
55
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
156

167
# Check if JAX-capable GPU is available, otherwise exit
178
check_jax_gpu()
@@ -35,37 +26,6 @@
3526
### load settings from JSON
3627
target_settings, advanced_settings, filters = load_json_settings(settings_path, filters_path, advanced_path)
3728

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-
6929
settings_file = os.path.basename(settings_path).split('.')[0]
7030
filters_file = os.path.basename(filters_path).split('.')[0]
7131
advanced_file = os.path.basename(advanced_path).split('.')[0]
@@ -96,52 +56,11 @@
9656
####################################
9757
####################################
9858
####################################
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-
12659
### 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}")
14564

14665
####################################
14766
# initialise counters
@@ -184,23 +103,12 @@
184103
trajectory_exists = any(os.path.exists(os.path.join(design_paths[trajectory_dir], design_name + ".pdb")) for trajectory_dir in trajectory_dirs)
185104

186105
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)
189107

190108
### 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)
204112
trajectory_metrics = copy_dict(trajectory._tmp["best"]["aux"]["log"]) # contains plddt, ptm, i_ptm, pae, i_pae
205113
trajectory_pdb = os.path.join(design_paths["Trajectory"], design_name + ".pdb")
206114

@@ -210,7 +118,9 @@
210118
# time trajectory
211119
trajectory_time = time.time() - trajectory_start_time
212120
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+
214124
# Proceed if there is no trajectory termination signal
215125
if trajectory.aux["log"]["terminate"] == "":
216126
# Relax binder to calculate statistics
@@ -229,18 +139,6 @@
229139

230140
# analyze interface scores for relaxed af2 trajectory
231141
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()
244142

245143
# starting binder sequence
246144
trajectory_sequence = trajectory.get_seq(get_best=True)[0]
@@ -262,6 +160,10 @@
262160
trajectory_alpha_interface, trajectory_beta_interface, trajectory_loops_interface, trajectory_alpha, trajectory_beta, trajectory_loops, trajectory_interface_AA, trajectory_target_rmsd,
263161
trajectory_time_text, traj_seq_notes, settings_file, filters_file, advanced_file]
264162
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
265167

266168
if advanced_settings["enable_mpnn"]:
267169
# initialise MPNN counters
@@ -338,7 +240,7 @@
338240

339241
# if AF2 filters are not passed then skip the scoring
340242
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")
342244
mpnn_n += 1
343245
continue
344246

@@ -474,7 +376,7 @@
474376
# run design data against filter thresholds
475377
filter_conditions = check_filters(mpnn_data, design_labels, filters)
476378
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")
478380
accepted_mpnn += 1
479381
accepted_designs += 1
480382

@@ -501,7 +403,7 @@
501403
shutil.copy(source_plot, target_plot)
502404

503405
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}")
505407
failure_df = pd.read_csv(failure_csv)
506408
special_prefixes = ('Average_', '1_', '2_', '3_', '4_', '5_')
507409
incremented_columns = set()
@@ -527,12 +429,15 @@
527429
break
528430

529431
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("")
531434
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("")
533437

534438
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("")
536441

537442
# save space by removing unrelaxed design trajectory PDB
538443
if advanced_settings["remove_unrelaxed_trajectory"]:
@@ -541,16 +446,14 @@
541446
# measure time it took to generate designs for one trajectory
542447
design_time = time.time() - design_start_time
543448
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)
547450

548451
# analyse the rejection rate of trajectories to see if we need to readjust the design weights
549452
if trajectory_n >= advanced_settings["start_monitoring"] and advanced_settings["enable_rejection_check"]:
550453
acceptance = accepted_designs / trajectory_n
551454
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...")
554457
break
555458

556459
# increase trajectory number
@@ -560,10 +463,4 @@
560463
### Script finished
561464
elapsed_time = time.time() - script_start_time
562465
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

Comments
 (0)