Skip to main content

Optimizers

Optimizers are the search algorithms of Proto. They coordinate generators and constraints in an iterative loop: generate proposal sequences, score them, select the best, and repeat. Different optimizers implement different search strategies, from rejection sampling to MCMC with simulated annealing.

The Optimization Loop

Every optimizer follows the same fundamental loop:
Initialize PoolsGenerate(generators propose proposals)Filter(hard constraints reject bad ones)Score(soft constraints compute energy)Select(keep best by energy)Done?Return ResultsYesNoENERGY AGGREGATIONfilter fails → E = ∞E = Σ wᵢ·sᵢ (add)E = Π wᵢ·sᵢ (multiply)
Initialize PoolsGenerate(generators propose proposals)Filter(hard constraints reject bad ones)Score(soft constraints compute energy)Select(keep best by energy)Done?Return ResultsYesNoENERGY AGGREGATIONfilter fails → E = ∞E = Σ wᵢ·sᵢ (add)E = Π wᵢ·sᵢ (multiply)
Energy scoring is where constraints come together. The optimizer calls score_energy() which:
  1. Evaluates all filter constraints first (hard pass/fail)
  2. Rejects proposals that fail any filter (energy = inf)
  3. Evaluates scoring constraints only on surviving proposals
  4. Combines scores: energy = Sigma(weight_i x score_i)

Optimizer Comparison

Available Optimizers

The general-purpose optimizer. Uses Markov Chain Monte Carlo with Metropolis-Hastings acceptance to explore sequence space. Maintains one or more parallel trajectories, proposing mutations and accepting or rejecting them based on energy improvement and temperature.How it works:
  1. For each result sequence, generate proposals_per_result proposals
  2. Score all proposals, pick the best one per result
  3. Accept or reject based on Metropolis-Hastings criterion:
    • Always accept if energy improves
    • Sometimes accept worse moves (controlled by temperature)
  4. Temperature anneals from max_temperature to min_temperature over the run
When to use: General-purpose optimization, protein refinement, any iterative design task. A reasonable default when the choice is unclear.
python
int | None
default:"None"
Number of parallel trajectories to maintain. num_results=1 is standard single-chain MCMC. Higher values explore more of sequence space in parallel.
int
required
Total number of MCMC steps. More steps allow better convergence but increase runtime.
int
default:"1"
Number of proposals to generate per result per step. Total proposals per step = num_results x proposals_per_result.
float
default:"1.0"
Starting temperature. Higher = more exploration (accepts worse moves more often).
float
default:"0.001"
Final temperature. Lower = more greedy (only accepts improvements).
The simplest optimizer. Generates a large number of proposals, scores them all, and keeps the top num_results by lowest energy. No iterative refinement; just sampling with selection.How it works:
  1. Generate proposals in batches of proposal_batch_size
  2. Score them all
  3. Track the top num_results proposals seen so far (maintained in sorted order)
  4. Repeat until num_samples reached (or energy_threshold met)
When to use: Initial exploration, quick screening, or as the first stage in a multi-stage Program pipeline.
python
int
required
Maximum number of proposals to generate.
int | None
default:"None"
Number of top sequences to keep (by lowest energy). Overrides the program-level num_results when set.
int | None
default:"None"
Number of proposal sequences to generate and evaluate per batch, capped at num_samples.
float
default:"None"
If set, enables early stopping: halts when all top-k proposals have energy below this threshold.
Optimizer for generating long sequences with autoregressive generators such as Evo2. Splits a long segment into chunks of beam_length tokens and performs beam search at each boundary, pruning low-quality beams as the sequence grows.How it works:
  1. Start from prompt sequence
  2. Generate beam_length tokens with proposals_per_result variations per beam
  3. Score all beams with constraints
  4. Keep top num_results beams
  5. Repeat until segment length is reached
When to use: Generating a single long DNA sequence (e.g., 2000+ bp) with quality constraints applied during generation rather than after.
python
BeamSearch requires a single-segment construct and an autoregressive generator. It ignores previous optimizer results in a Program; it always starts fresh from its configured prompt.
str
required
Initial sequence to begin generation from. All beams extend from this prompt.
int
required
Number of tokens to generate per beam step.
int
Number of top beams to maintain at each step (K in beam search). Optional; defaults to the number of results requested by the program.
int
required
Number of proposal extensions per beam. Total proposals per step = num_results x proposals_per_result.
str
default:"mean"
Aggregation method: "mean" (average across beams, rewards consistency) or "last" (most recent beam only).
bool
default:"False"
Enable KV cache reuse for faster autoregressive generation across beam steps.
A generalized optimizer that alternates between a user-defined conditioning function and a generator. The conditioning function can modify generator config between iterations; for example, predicting a 3D structure from the current sequence, then using that structure to condition inverse folding for the next iteration.How it works:
  1. Run the conditioning function on current sequences
  2. The conditioning function updates generator config (e.g., sets new PDB structures)
  3. Generator produces new proposals conditioned on updated config
  4. Optionally evaluate constraints and roll back rejected proposals
  5. Repeat for num_steps
When to use: Structure prediction + inverse folding cycles (“protein hallucination”), or any iterative conditioning workflow.Built-in pipeline: The protein-hunter pipeline automates the common pattern of structure prediction followed by inverse folding:
python
int | None
default:"None"
Number of proposal trajectories to maintain across cycles.
int
required
Number of conditioning-generation cycles to run.
str
default:"None"
Named pipeline (e.g., "protein-hunter") for common conditioning patterns.
Callable
default:"None"
Custom conditioning function for advanced use cases. Passed as a constructor argument to CyclingOptimizer (not a CyclingOptimizerConfig field), and mutually exclusive with pipeline.
The Gradient optimizer (continuous relaxation with differentiable constraints, paired with PositionWeightGenerator) is summarized in the comparison table above; see its full reference at Gradient optimizer.

Optimizer Decision Tree

What kind ofoptimization?Sequence type?DNA / RNAProteinLong sequence?(>500bp)Have / want todesign structure?BeamSearch+ Evo2Goal?Cycling(protein-hunter)Goal?Rejection SamplingMCMCRejection SamplingMCMCRejection Sampling then MCMC(use Program)YesNoIterativehallucinationNoQuickscreenOptimizeQuickscreenDetailedoptimizationMulti-stageGradientdifferentiable constraintsDifferentiable
What kind ofoptimization?Sequence type?DNA / RNAProteinLong sequence?(>500bp)Have / want todesign structure?BeamSearch+ Evo2Goal?Cycling(protein-hunter)Goal?Rejection SamplingMCMCRejection SamplingMCMCRejection Sampling then MCMC(use Program)YesNoIterativehallucinationNoQuickscreenOptimizeQuickscreenDetailedoptimizationMulti-stageGradientdifferentiable constraintsDifferentiable

Pool Architecture

Understanding the dual-pool system is key to understanding how optimizers work:

proposal_sequences

The working pool. Generators write proposals here. Constraints evaluate sequences from here. Size = num_proposals.It acts as an inbox: new proposals arrive, are evaluated, and the best ones graduate to the result pool.

result_sequences

The results pool. Contains the best sequences found so far. Size = num_results.It acts as a hall of fame: only the best-scoring sequences are retained here.

Pool Initialization (Cycling)

When an optimizer starts, either fresh or after receiving results from a previous optimizer in a Program, both pools are initialized by cycling through the source sequences:
This cycling preserves diversity when pool sizes differ between optimizers. Some optimizers (like Rejection Sampling) keep their results sorted by energy; others preserve their natural ordering.

Constraint Evaluation & Performance

The score_energy() method implements a two-pass evaluation strategy that skips expensive GPU computations on already-rejected proposals: Pass 1, Filters: All filter constraints (those with a threshold) are evaluated first. Proposals that fail any filter are immediately rejected with energy = inf and marked with the rejecting constraint’s label. This is an AND gate; a proposal must pass every filter to survive. Pass 2, Scoring: Scoring constraints (those with a weight) are only evaluated on proposals that passed all filters. This means expensive GPU evaluations (structure prediction, binding strength) are never run on proposals that already failed a cheap filter (homopolymer check, GC content range).
GPU memory for constraint evaluation is managed at the tool level, not the framework level. Unlike generators (which have a framework-level batch_size), constraints receive all passing proposals in a single call. Each tool handles its own memory internally: ESMFold splits by residue count, Boltz2 processes complexes sequentially, and so on. Users control this through tool-specific config fields (e.g., max_batch_residues for ESMFold) rather than a constraint-level parameter.
Constraints are ordered with cheap filters first (sequence composition checks) and expensive scoring constraints last (structure prediction, binding). The two-pass strategy ensures rejected proposals never trigger GPU evaluations.

Temperature and Acceptance

Temperature controls the exploration-exploitation trade-off in MCMC. It determines how willing the optimizer is to accept a proposal that is worse than the current best:
High Temperature (T=1.0)Low Temperature (T=0.001)Accepts most movesExplores broadlyGood for escapinglocal minimaOnly acceptsimprovementsExploits locallyGood for finalpolishingAnnealingover num_steps
High Temperature (T=1.0)Low Temperature (T=0.001)Accepts most movesExplores broadlyGood for escapinglocal minimaOnly acceptsimprovementsExploits locallyGood for finalpolishingAnnealingover num_steps
MCMC uses exponential annealing: T(step) = T_max x (T_min / T_max)^((step - 1) / (num_steps - 1)), so step 1 is exactly T_max and the final step is exactly T_min.
A common pattern is to start with high temperature for exploration, then anneal to low temperature for refinement. For multi-stage optimization, use a Program with explicit temperature stages instead of relying solely on annealing.

Tool Cache Management

Constraints that call expensive bioinformatics tools (structure prediction, sequence alignment) benefit from caching. The optimizer manages a shared tool cache:
python

History Tracking

Optimizers record snapshots of their state at configurable intervals for post-hoc analysis:
python

Next Steps

Programs

Chain multiple optimizers into multi-stage pipelines

Generators

The models that propose candidate sequences

Constraints

The quality checklist optimizers minimize

Optimizer Reference

Full API reference for each optimizer

Optimizer Catalog