Skip to content

molforge.design

design

The protein-design loop: generate → fold → (dock) → score → iterate.

Real protein engineering is a loop. You propose sequences for a scaffold, predict what they fold to, check whether the prediction actually matches the scaffold you designed for, keep the winners, and design again from them. Every piece already exists as a molforge wrapper — a sequence designer (ProteinMPNN, ESM-IF1), a folding engine (ESMFold, AlphaFold, Boltz), optionally a docking engine — but nothing glues them into the loop. :class:DesignLoop is that glue:

from molforge.design import DesignLoop
from molforge.wrappers.generative import ProteinMPNN
from molforge.wrappers.folding import ESMFold

loop = DesignLoop(designer=ProteinMPNN(), folder=ESMFold(), n_rounds=3)
table = loop.run(backbone)          # a Protein or a PDB path
best = table.best                   # highest self-consistency design
rows = table.to_records()           # flat dicts → drop into a DataFrame

The default objective is self-consistency: fold each designed sequence and measure how well the prediction superposes on the backbone it was designed for (scTM / scRMSD). This is the metric the RFdiffusion / ProteinMPNN / AlphaFold design pipelines are graded on, and it falls straight out of the corrected :func:~molforge.metrics.tm_score and :func:~molforge.structure.rmsd. Other built-in objectives score by folding confidence ("plddt") or docked affinity ("affinity"), and a custom callable covers everything else.

Folding can be a single engine or a list — pass a list and each candidate is folded with :func:~molforge.ensembles.cross_engine_fold, scored against the cross-engine consensus, with the per-residue cross-engine disagreement recorded as an extra confidence signal.

Iteration is genuine: round r+1 re-designs onto the folded structures of the top select_top candidates from round r, so the scaffold refines as the loop runs. The :class:DesignTable accumulates every candidate across every round, ranked best-first.

Scope (v1): the designer produces sequences for a provided backbone. The generator slot (round-0 backbone generation, e.g. RFdiffusion) is part of the signature but raises :class:NotImplementedError — its configuration surface (contigs, targets, symmetry) is too engine-specific to wire generically yet.

DesignCandidate dataclass

DesignCandidate(
    sequence: str,
    round: int,
    backbone: Protein | None = None,
    structure: Protein | None = None,
    docking: DockingResult | None = None,
    metrics: dict[str, float] = dict(),
    score: float = math.nan,
    metadata: dict[str, object] = dict(),
)

One design as it flows through the loop, accumulating results.

Attributes:

Name Type Description
sequence str

The designed one-letter sequence.

round int

0-indexed loop round that produced it.

backbone Protein | None

The scaffold this sequence was designed onto — the user's input backbone in round 0, a previous round's folded winner thereafter. The reference for self-consistency.

structure Protein | None

The folded (predicted) structure. None until the fold stage runs. For a list folder this is the cross-engine consensus.

docking DockingResult | None

The docking result against the receptor, if a docker was configured; otherwise None.

metrics dict[str, float]

Named scalar measurements — sc_tm, sc_rmsd, plddt, mpnn_score, affinity, and (for list folders) cross_engine_tm_mean / cross_engine_rmsf_mean.

score float

The objective value; higher is better. nan until scored.

metadata dict[str, object]

Free-form extras.

DesignTable dataclass

DesignTable(
    candidates: list[DesignCandidate],
    rounds: int,
    objective: str,
)

The ranked output of a :meth:DesignLoop.run.

Candidates from every round, sorted best-first by objective score (nan scores sort last).

Attributes:

Name Type Description
candidates list[DesignCandidate]

All designs, best-first.

rounds int

Number of rounds that ran.

objective str

The objective name (or "custom" for a callable).

best property

best: DesignCandidate

The top-scoring candidate. Raises IndexError if empty.

top_n

top_n(n: int) -> list[DesignCandidate]

The n best-scoring candidates.

to_records

to_records() -> list[dict[str, object]]

Flatten to a list of row dicts — pandas.DataFrame(...)-ready.

Each row has round, score, sequence, and one column per metric key present across the candidates (missing values filled with nan).

DesignLoop

DesignLoop(
    *,
    designer: GenerativeEngine,
    folder: FoldingEngine | Sequence[FoldingEngine],
    docker: DockingEngine | None = None,
    generator: GenerativeEngine | None = None,
    objective: DesignObjective
    | Callable[[DesignCandidate], float]
    | Scorer = "self_consistency",
    n_designs: int = 8,
    n_rounds: int = 1,
    select_top: int = 4,
    workers: int | None = None,
    backend: Backend | None = None,
)

Orchestrate generate → fold → (dock) → score → iterate.

Parameters:

Name Type Description Default
designer GenerativeEngine

A sequence-design engine — any object with generate(backbone, **kwargs) -> list[DesignedSequence] (ProteinMPNN, ESM-IF1).

required
folder FoldingEngine | Sequence[FoldingEngine]

A folding engine, or a list of them. A single engine folds each sequence with predict; a list folds via :func:~molforge.ensembles.cross_engine_fold and scores against the consensus.

required
docker DockingEngine | None

Optional docking engine. When set, each folded structure is docked against receptor (supplied to :meth:run) and the best pose's score is recorded as affinity.

None
generator GenerativeEngine | None

Reserved for round-0 backbone generation (RFdiffusion). Supplying it raises :class:NotImplementedError in v1.

None
objective DesignObjective | Callable[[DesignCandidate], float] | Scorer

"self_consistency" (default), "plddt", "affinity", or a custom Callable[[DesignCandidate], float]. Higher is better.

'self_consistency'
n_designs int

Max designed sequences to carry forward per backbone per round (the designer may propose more; the top n_designs by the designer's own score are kept).

8
n_rounds int

Number of design rounds.

1
select_top int

Winners carried as next-round seeds (their folded structures become the scaffolds redesigned onto).

4
workers int | None

Parallel-fold worker count (see :func:molforge.parallel).

None
backend Backend | None

Parallel-fold backend override; defaults to the folding engine's parallelism hint.

None

Raises:

Type Description
NotImplementedError

If generator is supplied.

ValueError

If objective="affinity" without a docker, or the numeric parameters are non-positive.

run

run(
    backbone: Protein | str | PathLike[str],
    *,
    receptor: Protein | None = None,
    designer_kwargs: dict[str, Any] | None = None,
    fold_kwargs: dict[str, Any] | None = None,
    dock_kwargs: dict[str, Any] | None = None,
) -> DesignTable

Run the loop and return a ranked :class:DesignTable.

Parameters:

Name Type Description Default
backbone Protein | str | PathLike[str]

The scaffold to design onto — a :class:Protein or a path to a PDB file. A path is loaded once for scoring.

required
receptor Protein | None

The docking receptor; required if a docker was configured.

None
designer_kwargs dict[str, Any] | None

Extra kwargs forwarded to designer.generate.

None
fold_kwargs dict[str, Any] | None

Extra kwargs forwarded to the folding call(s).

None
dock_kwargs dict[str, Any] | None

Extra kwargs forwarded to docker.dock.

None

Returns:

Name Type Description
A DesignTable

class:DesignTable of every candidate across all rounds,

DesignTable

best-first.

Raises:

Type Description
ValueError

If a docker is configured but receptor is None, or no candidate survives folding.