← Home

De Novo generation of proteins has matured

This post stands on the shoulders of giants. David Baker, Demis Hassabis, and John Jumper received the 2024 Nobel Prize in Chemistry for computational protein design and protein structure prediction, including protein structure prediction tools like AlphaFold and related methods. In just two years, the overall cost to deploy these technologies has plummeted. It is now possible not only to predict the folding of real-world proteins, but to also utilize this technology as a pipeline for the creation of novel proteins which solve particular functions.

tl;dr #

  • A standardized pipeline now consists of RFDiffusion -> ProteinMPNN -> AlphaFold, RoseTTAfold, ESMfold... as a pipeline to take conditional generation from inferred backbones, to inferred sequences, to folded proteins.
  • possible RFDiffusion design criteria:
    • specific AA-length
    • scaffold around a functional motif
    • design a protein binding to a target protein or ligand
    • design a new symmetric oligomer
  • RFDiffusion produces 'realistic' folding in backbone generation.
    • RMSD of structure compared to native structure and Alphafold outputs typically sub-Angstrom
  • limit: ~400 AA-length for backbone generation with RFDiffusion

Requirements #

Despite this, you probably need a 'higher-end' GPU. I've tested this on a MIG'd Nvidia Ampere A30 to emulate a consumer-sized card for testing: in this MIG configuration, only half of the card, providing a total of 12GB VRAM was used. The A30 is a PCIe card with 3804 CUDA cores, 224 Tensor cores, and 24GB of HBM2 memory. It is capable of 10.3 TFLOPS in FP32, 82 TFLOPS in TF32, and 165 TFLOPS in BF16/FP16 on Tensor core. It can be thought of as a scaled-down version of the A100.

Hard requirements include:

  • Linux x86_64
  • A Nvidia GPU compatible with CUDA12
  • about 8 GB of disk space
  • uv the cooler Python venv install manager
  • aria2 the coolest download manager

Install Process #

First, set up a Python virtual

uv venv --python 3.12
uv pip install rc-foundry[all]==0.2.0

Functional Motif Scaffolding Example #

Designing from a a catalytic triad, one can design a new protein as an enzyme 1euv_lig.pdb for another, as a template. This minimal example targets a file representing the ligand or interacting substrate component derived from the Protein Data Bank entry 1EUV, which captures the crystal structure of the yeast Ulp1 SUMO protease bound to its cellular substrate Smt3.

import numpy as np
from lightning.fabric import seed_everything
from rfd3.engine import RFD3InferenceConfig, RFD3InferenceEngine
from mpnn.inference_engines.mpnn import MPNNInferenceEngine
from rf3.inference_engines.rf3 import RF3InferenceEngine
from rf3.utils.inference import InferenceInput

# Set seed for 
seed_everything(42)

# Path to the input PDB with catalytic residues and ligand
INPUT_PDB = os.path.abspath(
    "1euv_lig.pdb"
)

# BACKBONE DESIGN
config = RFD3InferenceConfig(
    specification={
        # --- Input structure ---
        'input': INPUT_PDB,

        # --- Ligand identification ---
        # 'l:g' is a custom name (colon ensures it won't match the CCD)
        'ligand': 'l:g',

        # --- Unindexed motif residues ---
        # These catalytic residues will be placed somewhere in the scaffold.
        # RFD3 decides WHERE in the sequence they go.
        # A579-581 are listed as a contiguous block (they stay together).
        'unindex': 'A514,A531,A574,A579-581',

        # --- Which atoms stay fixed in 3D space ---
        # For catalytic residues: fix the sidechain atoms that do chemistry
        # For neighbors of cysteine (A579, A581): fix only backbone
        # For the nucleophilic cysteine (A580): fix entire residue
        'select_fixed_atoms': {
            'A514': 'NE2,CE1,ND1,CD2,CG,CB',   # His — imidazole ring
            'A531': 'OD1,CG,OD2,CB',             # Asp — carboxylate
            'A574': 'NE2,CD,OE1,CG',             # Gln — amide
            'A579': 'C,O,CA,N',                   # Asp neighbor — backbone only
            'A580': 'SG,CB,CA,N,C,O',             # Cys — entire residue (nucleophile)
            'A581': 'C,O,CA,N',                   # Gly neighbor — backbone only
        },

        # --- Allow sequence changes for backbone-only residues ---
        # A579 and A581 have fixed backbone but their amino acid identity
        # can change — we only care about the backbone geometry near the Cys.
        'select_unfixed_sequence': 'A579,A581',

        # --- RASA conditioning: control how buried the ligand is ---
        # Atoms near the active site should be buried inside the protein.
        # Atoms facing solvent should remain exposed.
        'select_buried': {
            'l:g': 'O1,C8,O3,C4,C5,C23,C24,C25,C26,C27'
        },
        'select_exposed': {
            'l:g': 'C2,C22,C19,C18,C17,C20,C16,C15,O21,O14,C13,C12'
        },

        # --- Protein length and center of mass ---
        'length': '100-200',
        'ori_token': [0, 1, 0],  # Near the center of the active site

        'extra': {},
    },
    diffusion_batch_size=4,  # Generate 4 scaffold designs
)

model = RFD3InferenceEngine(**config)
outputs = model.run(
    inputs=None,
    out_dir=None,    # Return in memory
    n_batches=1,
)

# Extract first design for downstream steps
first_key = next(iter(outputs.keys()))
scaffold = outputs[first_key][0].atom_array

engine_config = {
    "model_type": "ligand_mpnn",   # Ligand-aware: sees the substrate in context
    "is_legacy_weights": True,
    "out_directory": None,
    "write_structures": False,
    "write_fasta": False,
}

input_configs = [
    {
        "batch_size": 8,           # 8 sequences per scaffold
        "remove_waters": True,
    }
]

# SEQUENCE DESIGN
mpnn_model = MPNNInferenceEngine(**engine_config)
mpnn_outputs = mpnn_model.run(
    input_dicts=input_configs,
    atom_arrays=[scaffold],
)

print(f"Generated {len(mpnn_outputs)} designed sequences")

# STRUCTURE VALIDATION (REFOLD WITHOUT LIGAND)
inference_engine = RF3InferenceEngine(ckpt_path='rf3', verbose=False)

# Use first MPNN design, protein chain only
best_design = mpnn_outputs[0].atom_array
protein_only = best_design[best_design.hetero == False]

rf3_input = InferenceInput.from_atom_array(
    protein_only,
    example_id="enzyme_scaffold",
)
rf3_outputs = inference_engine.run(inputs=rf3_input)

print(f"RF3 models: {len(rf3_outputs['enzyme_scaffold'])}")

# Confidence metrics
rf3_output = rf3_outputs["enzyme_scaffold"][0]
summary = rf3_output.summary_confidences

print("=== RF3 Confidence Metrics ===")
print(f"  pLDDT:         {summary['overall_plddt']:.3f}")
print(f"  pTM:           {summary['ptm']:.3f}")
print(f"  PAE:           {summary['overall_pae']:.2f} Å")
print(f"  Ranking score: {summary['ranking_score']:.3f}")
print(f"  Has clash:     {summary['has_clash']}")

# viewing output in Jupyter 
# view(rf3_output.atom_array)

# Original scaffold (protein only) vs RF3 prediction
designed_protein = scaffold[scaffold.hetero == False]
predicted_protein = rf3_output.atom_array

# Filter to backbone atoms
bb_designed = designed_protein[
    np.isin(designed_protein.atom_name, PROTEIN_BACKBONE_ATOM_NAMES)
]
bb_predicted = predicted_protein[
    np.isin(predicted_protein.atom_name, PROTEIN_BACKBONE_ATOM_NAMES)
]

if len(bb_designed) == len(bb_predicted):
    bb_pred_fitted, _ = superimpose(bb_designed, bb_predicted)
    rmsd_val = rmsd(bb_designed, bb_pred_fitted)
    print(f"Backbone RMSD: {rmsd_val:.2f} Å")

    if rmsd_val < 1.0:
        print("Excellent designability — scaffold folds as intended")
    elif rmsd_val < 2.0:
        print("Good designability")
    elif rmsd_val < 3.0:
        print("Moderate — consider more MPNN sequences or different scaffold")
    else:
        print("Poor — this scaffold likely won't fold correctly")
else:
    print(f"Atom count mismatch: designed={len(bb_designed)}, predicted={len(bb_predicted)}")