Python API

July 1, 2026 · View on GitHub

Caliby provides a Python API for programmatic use in scripts and notebooks, without needing Hydra or the CLI. Install Caliby as usual (uv pip install -e . or uv pip install "git+https://github.com/ProteinDesignLab/caliby.git") and import directly:

from caliby import load_model

Quick start

from caliby import load_model

# Load the model once and reuse it.
model = load_model("caliby")

# Design 4 sequences for a PDB file.
results = model.sample(
    ["examples/example_data/native_pdbs/7xhz.cif"],
    num_seqs_per_pdb=4,
    out_dir="outputs/seq_des",
)

# results is a dict with keys: "example_id", "out_pdb", "seq", "U", "input_seq"
for name, seq, energy in zip(results["example_id"], results["seq"], results["U"]):
    print(f"{name}: {seq} (U={energy:.2f})")

Loading a model

Use load_model() to load a model checkpoint. The model is returned as a CalibyModel object that you can reuse across multiple calls.

from caliby import load_model

# Load by model name (weights are auto-downloaded from HuggingFace on first use).
model = load_model("caliby")                   # Default model
model = load_model("soluble_caliby")           # Excludes transmembrane proteins
model = load_model("soluble_caliby_v1")        # Trained on monomers and interfaces
model = load_model("caliby_distill")           # Distilled, no ensembles needed
model = load_model("soluble_caliby_distill")   # Distilled, no ensembles needed

# Load from a custom checkpoint path.
model = load_model("/path/to/custom_model.ckpt")

# Specify device (defaults to "cuda" if available, else "cpu").
model = load_model("caliby", device="cuda:1")

Available model names are listed in the Download model weights section of the README.

Cleaning input structures

We recommend cleaning input PDB/CIF files before sequence design, especially if the structures came from another pipeline or if you plan to generate ensembles with Protpardelle-1c. The cleaner fixes blank chain IDs, removes unresolved atoms, keeps only protein chains, filters to residue names supported by downstream tools, and writes cleaned mmCIF files. This helps avoid downstream parsing, chain ID, and residue alignment issues.

from caliby import clean_pdbs, load_model

cleaned_pdb_paths = clean_pdbs(
    ["protein1.pdb", "protein2.cif"],
    out_dir="outputs/cleaned_pdbs",
    num_workers=4,
)

model = load_model("caliby")
results = model.sample(cleaned_pdb_paths, num_seqs_per_pdb=4)

Sequence design

Design sequences for one or more PDB/CIF files:

model = load_model("caliby")

results = model.sample(
    ["protein1.pdb", "protein2.cif"],
    num_seqs_per_pdb=4,       # 4 sequences per structure
    batch_size=4,             # batch size for processing
    out_dir="outputs/",       # directory for output CIF files (temp dir if None)
)

# Access results.
results["example_id"]  # e.g. ["protein1_0", "protein1_1", ...]
results["seq"]         # designed sequences
results["U"]           # Potts energy of each designed sequence
results["out_pdb"]     # paths to output CIF files
results["input_seq"]   # native sequence of the input structure

To design sequences for all PDBs in a directory:

from pathlib import Path

pdb_dir = Path("examples/example_data/native_pdbs")
pdb_paths = [str(p) for p in sorted(pdb_dir.glob("*.cif"))]

results = model.sample(pdb_paths, num_seqs_per_pdb=4, out_dir="outputs/seq_des")

Ensemble-conditioned sequence design

Designing on synthetic structural ensembles (rather than a single static structure) produces sequences that are both more diverse and more likely to fold into the target structure.

Generating ensembles with Protpardelle-1c

Use generate_ensembles() to generate conformer ensembles via Protpardelle-1c partial diffusion:

from caliby import generate_ensembles, load_model

# Generate 32 conformers per PDB.
pdb_to_conformers = generate_ensembles(
    ["examples/example_data/native_pdbs/7xhz.cif"],
    out_dir="outputs/ensembles",
    num_samples_per_pdb=32,
    batch_size=8,
)
# pdb_to_conformers: {"7xhz": ["outputs/ensembles/.../sample_0.pdb", ...]}

# Run ensemble-conditioned sequence design.
model = load_model("caliby")
results = model.ensemble_sample(
    pdb_to_conformers,
    num_seqs_per_pdb=4,
    out_dir="outputs/seq_des_ensemble",
)

We recommend generating at least 32 conformers per PDB, but 16 or 8 can also give good results.

Providing your own ensembles

If you have your own ensemble of conformers, pass them as a dict mapping PDB name to a list of file paths. The first path in each list is treated as the primary conformer:

pdb_to_conformers = {
    "my_protein": [
        "ensembles/my_protein/my_protein.pdb",    # primary conformer (first)
        "ensembles/my_protein/conformer_1.pdb",
        "ensembles/my_protein/conformer_2.pdb",
        # ... up to max_num_conformers (default 32)
    ],
}

results = model.ensemble_sample(
    pdb_to_conformers,
    num_seqs_per_pdb=4,
    out_dir="outputs/seq_des_ensemble",
)

All conformers must have matching residue indices and chain IDs. If you encounter a "Residue index / chain ID mismatch" error, see the FAQ in the README.

Scoring

Single-structure scoring

Score the native sequences of PDB/CIF files:

model = load_model("caliby")

results = model.score(["protein.pdb"])

results["example_id"]  # PDB identifiers
results["seq"]         # native sequences
results["U"]           # global Potts energy
results["U_i"]         # per-residue energy contributions

Ensemble scoring

Score a sequence against an ensemble of conformer backbones:

pdb_to_conformers = {
    "my_protein": [
        "ensembles/my_protein/my_protein.pdb",   # primary conformer
        "ensembles/my_protein/conformer_1.pdb",
        "ensembles/my_protein/conformer_2.pdb",
    ],
}

results = model.score_ensemble(pdb_to_conformers)

The sequence from the primary conformer is scored; sequences of additional conformers are ignored.

Positional constraints

Use make_constraints() to build a constraint DataFrame, then pass it to sample() or ensemble_sample():

from caliby import make_constraints

constraints = make_constraints({
    "7xhz": {
        "fixed_pos_seq": "A6-15,A20-50",
        "fixed_pos_scn": "A6-15",
    },
})

results = model.sample(
    ["examples/example_data/native_pdbs/7xhz.cif"],
    num_seqs_per_pdb=4,
    pos_constraint_df=constraints,
)

You can also pass a pd.DataFrame directly with a pdb_key column and any subset of the constraint columns described below.

Fixed positions

Fix certain residue positions so they retain their native sequence during design. Residue positions should use label_seq_id (not auth_seq_id). In PyMOL, run set cif_use_auth, off before loading a PDB to view positions in this numbering.

constraints = make_constraints({
    "7xhz": {
        "fixed_pos_seq": "A6-15,A20-50",   # fix sequence at these positions
        "fixed_pos_scn": "A6-15",           # also fix sidechains (must be subset of fixed_pos_seq)
    },
})

Amino acid restrictions

Restrict which amino acids are allowed at specific positions:

constraints = make_constraints({
    "8huz": {
        "pos_restrict_aatype": "A6:QR,A7:QR,A8:QR,A9:QR,A10:QR,A11:QR",
    },
})

Sequence overrides

Override the sequence at specific positions before conditioning:

constraints = make_constraints({
    "7xz3": {
        "fixed_pos_override_seq": "A36:C,A37:C,A38:C,A39:C,A40:C",
    },
})

Symmetry positions

Tie sampling across residue positions (e.g., for homooligomers):

constraints = make_constraints({
    "my_trimer": {
        "symmetry_pos": "A10,B10,C10|A11,B11,C11|A12,B12,C12",
    },
})

Positions separated by commas within a group are tied together. Groups are separated by |.

Combining constraints

Multiple constraint types can be combined for the same PDB, and constraints for multiple PDBs can be specified in a single call:

constraints = make_constraints({
    "7xhz": {
        "fixed_pos_seq": "A6-15",
        "pos_restrict_aatype": "A20:AVG,A21:AVG",
    },
    "8huz": {
        "pos_restrict_aatype": "A6:QR,A7:QR",
    },
})

results = model.sample(pdb_paths, num_seqs_per_pdb=4, pos_constraint_df=constraints)

Constraints for ensemble-conditioned design

When using ensemble_sample(), constraints must be expanded so that every conformer in the ensemble gets a matching row. Use make_ensemble_constraints() to handle this automatically:

from caliby import make_ensemble_constraints

# pdb_to_conformers from generate_ensembles() or your own ensemble
pdb_to_conformers = {
    "7xhz": ["7xhz.cif", "conformer_0.pdb", "conformer_1.pdb"],
}

constraints = make_ensemble_constraints(
    {"7xhz": {"fixed_pos_seq": "A6-15", "pos_restrict_aatype": "A20:AVG"}},
    pdb_to_conformers,
)

results = model.ensemble_sample(
    pdb_to_conformers,
    num_seqs_per_pdb=4,
    pos_constraint_df=constraints,
)

This is equivalent to calling make_constraints() and then manually replicating each row for every conformer path, but is less error-prone.

Additional sampling options

Omitting amino acids

Globally exclude certain amino acids from all designed positions:

results = model.sample(pdb_paths, omit_aas=["C", "M"])

Sampling temperature

By default, Caliby anneals the sampling temperature from 1.0 to 0.01. Raise the final temperature for more diverse sequences:

results = model.sample(pdb_paths, temperature=0.1)

Temperatures from 0.1 to 0.2 also perform reasonably, but this is case-dependent.

Advanced overrides

Any parameter from the sampling config can be overridden via sampling_overrides:

results = model.sample(
    pdb_paths,
    sampling_overrides={
        "potts_sampling_cfg": {
            "potts_sweeps": 1000,
            "regularization": "LCP",
        },
    },
)

Sidechain packing

Pack sidechains onto backbone structures using a diffusion-based packer. Note that sidechain packing uses a different model checkpoint from sequence design:

packer = load_model("caliby_packer_010")  # 0.1A noise (recommended)

results = packer.sidechain_pack(
    ["protein.pdb"],
    out_dir="outputs/packed",
)

results["example_id"]  # PDB identifiers
results["out_pdb"]     # paths to packed output CIF files

Available packer models: caliby_packer_000 (0.0A), caliby_packer_010 (0.1A, recommended), caliby_packer_030 (0.3A).

Self-consistency evaluation with AlphaFold2

Evaluate designed sequences by folding them with single-sequence AlphaFold2 and comparing against the design input. Requires the af2 extra:

uv pip install -e ".[af2]"
model = load_model("caliby")

# Design sequences.
results = model.sample(pdb_paths, num_seqs_per_pdb=2, out_dir="outputs/designed")

# Fold and evaluate.
sc_results = model.self_consistency_eval(
    results["out_pdb"],
    out_dir="outputs/af2_eval",
    num_models=5,
    num_recycles=3,
)

# sc_results: {"protein_0": {"sc_ca_rmsd": 1.2, "avg_ca_plddt": 85.3, "tmalign_score": 0.95}, ...}
for example_id, metrics in sc_results.items():
    print(f"{example_id}: scRMSD={metrics['sc_ca_rmsd']:.2f}, "
          f"pLDDT={metrics['avg_ca_plddt']:.1f}, TM={metrics['tmalign_score']:.3f}")

One-shot convenience functions

For quick, one-off runs where you don't need to reuse the model, Caliby provides module-level convenience functions that load the model, run the task, and return results:

from caliby import (
    caliby_sample,
    caliby_ensemble_sample,
    caliby_score,
    caliby_score_ensemble,
    caliby_sidechain_pack,
)

# These load the model each time — prefer load_model() for repeated calls.
results = caliby_sample(["protein.pdb"], num_seqs_per_pdb=4)
results = caliby_score(["protein.pdb"])
results = caliby_sidechain_pack(["protein.pdb"], model_name="caliby_packer_010")

API reference

load_model(model_name, device, sampling_cfg_path) -> CalibyModel

Load a Caliby model for reuse across multiple calls.

ParameterTypeDefaultDescription
model_namestr"caliby"Model name or path to .ckpt file
devicestr | NoneNoneTorch device (defaults to "cuda" if available)
sampling_cfg_pathstr | NoneNoneCustom sampling YAML config path

clean_pdbs(pdb_paths, out_dir, num_workers) -> list[str]

Clean input PDB/CIF files and write cleaned mmCIF copies.

ParameterTypeDefaultDescription
pdb_pathslist[str]requiredPaths to input PDB/CIF files
out_dirstr | NoneNoneOutput directory for cleaned mmCIF files
num_workersint1Number of parallel workers

Returns: list[str] of cleaned mmCIF paths in the same order as pdb_paths.

CalibyModel.sample()

ParameterTypeDefaultDescription
pdb_pathslist[str]requiredPaths to PDB/CIF files
out_dirstr | NoneNoneOutput directory (temp dir if None)
num_seqs_per_pdbint | None1Sequences per structure
batch_sizeint | None4Batch size
omit_aaslist[str] | NoneNoneAmino acids to exclude
num_workersint | None2Data loading workers
temperaturefloat | None0.01Final Potts sampling temperature
verbosebool | NoneTruePrint constraint info
pos_constraint_dfDataFrame | NoneNonePositional constraints
sampling_overridesdict | NoneNoneAdvanced config overrides

Returns: dict with keys "example_id", "out_pdb", "seq", "U", "input_seq".

CalibyModel.ensemble_sample()

Same parameters as sample(), plus:

ParameterTypeDefaultDescription
pdb_to_conformersdict[str, list[str]]requiredMaps PDB name to conformer paths (first is primary)
use_primary_res_typeboolTrueUse residue types from the primary conformer

CalibyModel.score()

ParameterTypeDefaultDescription
pdb_pathslist[str]requiredPaths to PDB/CIF files
batch_sizeint | None4Batch size
num_workersint | None2Data loading workers
sampling_overridesdict | NoneNoneAdvanced config overrides

Returns: dict with keys "example_id", "seq", "U", "U_i".

CalibyModel.score_ensemble()

ParameterTypeDefaultDescription
pdb_to_conformersdict[str, list[str]]requiredMaps PDB name to conformer paths
num_workersint | None2Data loading workers
sampling_overridesdict | NoneNoneAdvanced config overrides

Returns: dict with keys "example_id", "seq", "U", "U_i".

CalibyModel.sidechain_pack()

ParameterTypeDefaultDescription
pdb_pathslist[str]requiredPaths to PDB/CIF files
out_dirstr | NoneNoneOutput directory (temp dir if None)
batch_sizeint | None4Batch size
num_workersint | None2Data loading workers
sampling_overridesdict | NoneNoneAdvanced config overrides

Returns: dict with keys "example_id", "out_pdb".

CalibyModel.self_consistency_eval()

ParameterTypeDefaultDescription
designed_pdbslist[str]requiredPaths to designed PDB/CIF files
out_dirstr | NoneNoneOutput directory (temp dir if None)
num_modelsint5Number of AF2 models to sample
sample_modelsboolTrueRandomly sample from the 5 AF2 models
num_recyclesint3AF2 recycling iterations
use_multimerboolFalseUse AF2-Multimer

Returns: dict mapping example_id to {"sc_ca_rmsd", "avg_ca_plddt", "tmalign_score"}.

make_constraints(constraints) -> DataFrame

Build a positional constraint DataFrame from a dict.

make_constraints({"pdb_key": {"fixed_pos_seq": "A1-50", ...}})

make_ensemble_constraints(constraints, pdb_to_conformers) -> DataFrame

Build a constraint DataFrame expanded across ensemble conformers. Wraps make_constraints() and replicates each PDB's constraints for every conformer.

make_ensemble_constraints(
    {"7xhz": {"fixed_pos_seq": "A6-15"}},
    {"7xhz": ["7xhz.cif", "conf_0.pdb", "conf_1.pdb"]},
)

generate_ensembles()

ParameterTypeDefaultDescription
pdb_pathslist[str]requiredPaths to input PDB/CIF files
out_dirstrrequiredOutput directory
num_samples_per_pdbint32Conformers per structure
batch_sizeint8Batch size for Protpardelle
model_params_pathstr | NoneNoneModel weights directory
sampling_yaml_pathstr | NoneNoneProtpardelle config path
seedint0Random seed

Returns: dict mapping PDB stem to list of generated conformer file paths.