Architecture
Inside the cyclic design pipeline.
Five stages, three concentric levels of early termination, one cyclic-specific retry. The whole machinery is ~250 lines of Python on top of stock BindCraft v1.5.3.
At a glance
The 5-stage pipeline.
Read it left-to-right: each stage consumes the previous stage's output and either advances the design or short-circuits it into a rejection bucket.
Mermaid equivalent
flowchart TD
IN["3 JSON configs
(target / advanced / filters)"] --> S1[Stage 1: Target ingestion
+ cyclic_reses mask]
S1 --> S2[Stage 2: AF2 hallucination
cyclic relpos + bonded edge + bond loss]
S2 --> S2F{trajectory filters
clash, pLDDT, contacts}
S2F -->|fail| REJ1[Trajectory/LowConfidence
or Trajectory/Clashing]
S2F -->|pass| S3[Stage 3: pr_relax_cyclic
DeclareBond + bond constraints]
S3 --> S4[Stage 4: ProteinMPNN
soluble weights, fix interface]
S4 --> S5[Stage 5: AF2 re-prediction
cyclic relpos applied]
S5 --> S5R[pr_relax_cyclic + InterfaceAnalyzer]
S5R --> S5F{run_cyclic_filters
+ JSON filter engine}
S5F -->|fail| REJ2[Rejected/ + failure_csv]
S5F -->|pass| ACC[Accepted/]
ACC --> COUNT{#Accepted ≥ N?}
COUNT -->|no| S1
COUNT -->|yes| RANK[rank by Average_i_pTM
write final_design_stats.csv
exit]
Stage 1
Target ingestion & hotspot prep.
Modules: bindcraft.py outer loop (lines ~71–122);
functions/colabdesign_utils.py:binder_hallucination (entry);
functions/cyclic_utils.py:build_cyclic_reses_mask.
Input: Three JSON files; one target PDB; hotspot string (e.g. A48,A53,A66).
Input
3 JSON configs + target PDB + hotspot spec
Process
load_json_settings → perform_input_check → per trajectory: sample length, build cyclic_reses mask, prep_inputs
Output
af_model instance with target as chain A, binder as chain B, and af_model._cyclic_reses attached
def build_cyclic_reses_mask(num_residues: int,
binder_len: int,
target_len: int,
binder_is_cyclic: bool = True) -> np.ndarray:
"""Build a 1-D bool mask: True for binder residues (the tail)."""
mask = np.zeros(num_residues, dtype=bool)
if not binder_is_cyclic or binder_len <= 0:
return mask
mask[num_residues - binder_len:] = True
return mask
Stage 2
AF2 hallucination with cyclic relpos + bond loss.
Modules: functions/colabdesign_utils.py:binder_hallucination (modified);
functions/cyclic_utils.py:cyclize_relpos, cyclic_bonded_edges,
add_cyclic_bond_loss.
Input
Configured af_model + cyclic_reses mask
Process
3-stage design (logits → soft → pssm_semigreedy) with cyclized relpos, cyclic bonded edge, hard amide-bond loss; trajectory-level clash/pLDDT/contact filters
Output
Trajectory/<design>.pdb — chain A target + chain B binder, linear coords; N(1)···C(L) typically within ~4 Å
Cyclic patch (replaces add_termini_distance_loss): When cyclic_binder=true, add_cyclic_bond_loss(af_model, weight=0.5, target_N_C=1.33, target_CA_CA=3.8, target_omega=180.0) is registered. On every AF2 forward pass:
- Extract binder atoms from
outputs["structure_module"]["final_atom_positions"]sliced as[-self._binder_len:]. - Three harmonic penalties:
|dist(N(1), C(L)) − 1.33|²,|dist(Cα(1), Cα(L)) − 3.8|²,|dihedral(Cα(L), C(L), N(1), Cα(1)) − 180°|²(wrapped to [−180°, 180°]). - Returns
{"cyclic_bond": sum_of_penalties}registered withaf_model.opt["weights"]["cyclic_bond"] = weight.
Cyclic AF2 forward-pass diagram
Stage 3
Constrained FastRelax with DeclareBond.
Module: functions/pyrosetta_utils.py:pr_relax_cyclic (new function in the cyclic patch); falls back to stock pr_relax if cyclic_binder=false.
Input
Trajectory/<design>.pdb
Process
pose_from_pdb → DeclareBond(res(L):C, res(1):N) → 4 constraints → FastRelax with chainbreak=5.0 → AlignChainMover → clean_pdb_with_conect
Output
Trajectory/Relaxed/<design>.pdb — chemically cyclic pose, CONECT record, REMARK 470. NC ≈ 1.33 Å, ω ≈ 180°.
Cyclic FastRelax diagram
Stage 4
ProteinMPNN sequence redesign.
Module: functions/colabdesign_utils.py:mpnn_gen_sequence (unchanged from stock).
Input
Trajectory/Relaxed/<design>.pdb; binder interface residue list (KD-tree, 4 Å cutoff)
Process
mk_mpnn_model(weights="soluble", model="v_48_020"); fix_pos = "A,<interface>"; rm_aa='C'; sample 20 sequences at temp 0.1
Output
Up to 20 candidate sequences for the trajectory
Cyclic note
ProteinMPNN does not need a cyclic mode. It operates on the binder's backbone coordinates as a single chain; the cyclic geometry (closed ring) is preserved structurally because the input PDB already has N(1) and C(L) within peptide-bond distance. MPNN's chain-aware decoder treats the binder as one linear sequence, which is the correct inductive bias for predicting amino acids on a cyclic backbone.
Stage 5
AF2 re-prediction + relax + cyclic-aware filtering.
Modules: functions/colabdesign_utils.py:predict_binder_complex (modified);
functions/pyrosetta_utils.py:pr_relax_cyclic + score_interface;
functions/cyclic_utils.py:run_cyclic_filters;
functions/generic_utils.py:check_filters.
Input
Up to 20 MPNN sequences per trajectory
Process
AF2 monomer models 1, 2 with cyclic relpos applied; 3 recycles; per-model early filters; pr_relax_cyclic; InterfaceAnalyzer → dG, dSASA, SAP, CMS; run_cyclic_filters appends 3 cyclic columns; JSON filter engine tests every threshold
Output
Accepted/<design>_model<N>.pdb or Rejected/<...> + failure_csv.csv row
run_cyclic_filters computes three cyclic-specific columns and appends them to the design dict:
cyclic_NC_distance— N(1)–C(L) atom distance in Å (from the final relaxed PDB)cyclic_omega—Cα(L)–C(L)–N(1)–Cα(1)dihedral in degrees (wrapped to [−180, 180], absolute value stored)cyclic_CACA_distance—Cα(1)–Cα(L)distance in Å
Data structures
How a cyclic binder is represented at each stage.
| Stage | Representation | Length / Shape | Where it lives |
|---|---|---|---|
| 1. Ingest | target_settings, advanced_settings, filters (dicts) | JSON | Python dicts in bindcraft.py |
| 1. Ingest | AF2 model with cyclic_reses attached | (L_target + L_binder,) | af_model._cyclic_reses |
| 2. Hallucination | AF2 feature dict: aatype, residue_index, relpos (wrapped), bonded (cyclic-augmented) | shapes as above | af_model._inputs |
| 2. Hallucination | Loss callbacks: cyclic_bond + stock losses | scalar | af_model._callbacks["model"]["loss"] |
| 2. Output | Trajectory/<design>.pdb (linear coords) | 2 chains | disk |
| 3. Relax | PyRosetta pose with DeclareBond + 4 constraints | pose | RAM |
| 3. Output | Trajectory/Relaxed/<design>.pdb (cyclic, CONECT, REMARK 470) | 2 chains | disk |
| 4. MPNN | ProteinMPNN sample dict {seq, score, mask} | up to 20 seqs | RAM |
| 4. Output | MPNN/Sequences/<design>.fasta | text | disk |
| 5. Validation | predict_binder_complex returns per-model AF2 outputs | dict | RAM |
| 5. Output | MPNN/Binder/<design>_model{1,2}.pdb, MPNN/Relaxed/<...>.pdb | 2 chains | disk |
| 5. Filtering | design_dict (one CSV row): all metrics + 3 cyclic columns | dict | RAM |
| 5. Output | Accepted/ or Rejected/ PDBs; CSV rows | 2 chains + CSV | disk |
| Rank | final_design_stats.csv sorted by Average_i_pTM desc | CSV | disk |
Failure modes & retries
Three concentric levels of early termination.
Cyclic BindCraft has three concentric levels of early termination, mirroring stock BindCraft plus a cyclic-specific retry:
-
In-stage early termination (Stage 2): If pLDDT drops below 0.65 between design stages, ColabDesign skips the remaining stages and dumps the trajectory into
Trajectory/LowConfidence/. No retry; the outer loop moves to a new seed. -
Trajectory-level termination (Stage 2): Cα clash (
>0), pLDDT < 0.7, or<3hotspot contacts →Trajectory/Clashing/orTrajectory/LowConfidence/. No retry. -
Per-MPNN-sequence AF2 model termination (Stage 5): If any of
pLDDT,pTM,i_pTM,pAE,i_pAEfails for AF2 model 1 or 2, skip Rosetta scoring for that MPNN sequence. Try the next MPNN sequence. -
Final filter failure (Stage 5):
check_filtersreturns a list of failed columns. The design is moved toRejected/; the failed columns are appended tofailure_csv.csv. Up tomax_mpnn_sequences=2MPNN sequences per trajectory can still pass. -
Cyclic-specific retry: If
cyclic_NC_distance > 5 Åon the trajectory's relaxed PDB (FastRelax failed to close the ring), the cyclic patch marks the trajectory asCyclicClosureFailedand the outer loop tries a new seed. If this happens >20 times in a row, the cyclic bond loss weight is auto-bumped by 0.5 (up to a cap of 2.0) for subsequent trajectories.
Throughput
This retry logic keeps the pipeline productive: typical runs see ~50% of trajectories terminated early by Stage 2 filters, ~30% rejected by Stage 5 filters, and ~20% accepted.